DEV Community

Cover image for JavaScript Memory Model: Understanding Data Types, References, and Garbage Collection
Moiz Ali
Moiz Ali

Posted on

JavaScript Memory Model: Understanding Data Types, References, and Garbage Collection

JavaScript's handling of memory and data types is often misunderstood, leading to bugs and performance issues. This article takes an investigative approach to examine how JavaScript manages memory through practical code examples.


Part 1: The Two Worlds of JavaScript Data Types

Primitive vs Reference Types: A Fundamental Distinction

// Primitive example
let a = 5;
let b = a;
a = 10;
console.log(a); // 10
console.log(b); // 5

// Reference example
let x = [1, 2, 3];
let y = x;
x.push(4);
console.log(x); // [1, 2, 3, 4]
console.log(y); // [1, 2, 3, 4]
Enter fullscreen mode Exit fullscreen mode

Investigation #1: Let's examine what's happening in memory:

Primitive values are stored directly in the variable:

  • When b = a happens, the value 5 is copied from a to b
  • When a changes to 10, b remains unchanged

Reference values store a pointer to the data:

  • When y = x happens, both variables reference the same array
  • When we modify the array through x, y sees those changes

The Definitive List of Data Types

  • Primitive Types:

    • String
    • Number
    • Boolean
    • Undefined
    • Null
    • Symbol
    • BigInt
  • Reference Types:

    • Object (including Arrays, Functions, Dates, RegExps, Maps, Sets, etc.)

Part 2: Diving into Reference Memory Model

Behind-the-Scenes Investigation

Let's probe deeper with some experiments:

// Experiment 1: Objects and equality
let obj1 = { name: "Alice" };
let obj2 = { name: "Alice" };
let obj3 = obj1;

console.log(obj1 === obj2); // false
console.log(obj1 === obj3); // true
Enter fullscreen mode Exit fullscreen mode

Investigation #2: Why do identical-looking objects compare as not equal?

  • obj1 and obj2 point to different memory locations
  • obj1 and obj3 point to the same memory location
// Experiment 2: Modifying through references
let person = { name: "Bob", age: 30 };
let jobProfile = { person: person, title: "Developer" };

person.age = 31;
console.log(jobProfile.person.age); // 31
Enter fullscreen mode Exit fullscreen mode

Investigation #3: Nested objects share references

Changing person affects what you see through jobProfile.person

Visualizing Memory References

// Reassignment vs. modification
let arr1 = [1, 2, 3];
let arr2 = arr1;

// Modification (affects both references)
arr1.push(4);
console.log("After modification:");
console.log(arr1); // [1, 2, 3, 4]
console.log(arr2); // [1, 2, 3, 4]

// Reassignment (only affects one variable)
arr1 = [5, 6, 7];
console.log("After reassignment:");
console.log(arr1); // [5, 6, 7]
console.log(arr2); // [1, 2, 3, 4]
Enter fullscreen mode Exit fullscreen mode

Investigation #4: Two different operations:

  • Modification: Changes the data both variables point to
  • Reassignment: Makes a variable point to new data

Part 3: Garbage Collection in Practice

When Does Memory Get Freed?

// Creating potentially unused objects
function createObjects() {
  let tempArray = new Array(1000).fill("data");

  // This object becomes unreachable after the function returns
  let unretainedObject = { huge: new Array(10000).fill("more data") };

  // This object will be returned and retained
  let retainedObject = { name: "I survive" };

  return retainedObject;
}

let survivor = createObjects();
// When does unretainedObject get garbage collected?
Enter fullscreen mode Exit fullscreen mode

Investigation #5: Tracing object lifecycles

  • unretainedObject becomes eligible for GC when createObjects() returns
  • retainedObject remains in memory because it's assigned to survivor

Memory Leaks: When References Persist

// Potential memory leak through closures
function setupHandler() {
  let largeData = new Array(10000).fill("lots of data");

  return function() {
    // This closure maintains a reference to largeData
    console.log("Handler using", largeData.length, "items");
  };
}

let handler = setupHandler();
// largeData remains in memory as long as handler exists
Enter fullscreen mode Exit fullscreen mode

Investigation #6: Unintended retention

  • Even though we never directly access largeData again, it stays in memory
  • The closure in the returned function maintains a reference

Part 4: Practical Implications

Copying Objects: Shallow vs. Deep Copy

// Shallow copy
let original = { name: "Original", details: { id: 123 } };
let shallowCopy = { ...original }; // or Object.assign({}, original)

shallowCopy.name = "Changed";
shallowCopy.details.id = 456;

console.log(original.name);       // "Original" (primitive value was copied)
console.log(original.details.id); // 456 (reference value was shared)
Enter fullscreen mode Exit fullscreen mode

Investigation #7: The limits of spread operator and Object.assign

  • They only create a shallow copy
  • Nested objects are still shared references

Performance Considerations

// Creating many short-lived objects
function processData(items) {
  let results = [];

  for (let i = 0; i < items.length; i++) {
    // Each iteration creates new temporary objects
    let temp = {
      processed: items[i] * 2,
      original: items[i]
    };
    results.push(temp.processed);
  }

  return results;
}

const data = new Array(10000).fill(0).map((_, i) => i);
console.time("processing");
processData(data);
console.timeEnd("processing");
Enter fullscreen mode Exit fullscreen mode

Investigation #8: Object creation overhead

  • Creating many small objects can impact performance
  • Garbage collector has to work harder to clean up

Understanding JavaScript's memory model isn't just academic—it impacts how we write code daily. By grasping the difference between primitives and references, you can:

  1. Avoid unintended side effects when passing objects to functions
  2. Make informed decisions about copying data
  3. Prevent memory leaks by understanding object lifecycle
  4. Write more performant code by being mindful of object creation

Remember: In JavaScript, we don't just work with values—we work with references to values. Keeping this mental model clear will make you a more effective JavaScript developer.

Neon image

Set up a Neon project in seconds and connect from a Next.js application

If you're starting a new project, Neon has got your databases covered. No credit cards. No trials. No getting in your way.

Get started →

Top comments (0)

Sentry image

See why 4M developers consider Sentry, “not bad.”

Fixing code doesn’t have to be the worst part of your day. Learn how Sentry can help.

Learn more

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay