completefrontend
Open lab My progress
THE JAVASCRIPT FIELD GUIDE

Memory: primitives vs objects

Build an honest mental model for JavaScript memory: stack frames, heap objects, references, reachability, and garbage collection basics.

What you’ll be able to explain
  • 01
    Use the model carefullyDescribe the stack and the heap as a way to think, not as a map of real memory.
  • 02
    Predict shared objectsTell when a copy is separate and when two names share one object.
  • 03
    Reason about how long objects liveUse reachability to spot memory leaks, and explain why a cycle can still be cleaned up.

Where values live, and why copying an object shares it

In the Data types lesson, you met two kinds of values:

  • Primitives: simple values such as numbers, strings, booleans, null, and undefined.
  • Objects: everything else, such as arrays, functions, and plain objects.

In the Objects & references lesson, you saw that copying an object variable doesn't copy the object. Both variables then share one object. So a change made through one name shows up through the other name too. This lesson gives you a picture of memory that explains why that happens.

The picture has two places:

  • The stack: each function that is running gets a small area here, called a frame. The frame holds that function's variables.
  • The heap: a big storage area where objects live.

This stack-and-heap picture is a mental model: a simple way to think that helps you predict what code does. It is not an exact map of real memory. The engine is the program that runs your JavaScript, such as V8 in Chrome. An engine may store values however it likes, as long as your code gives the same results. So use the model to predict results. Don't use it to guess where things really sit in memory.

The whole lesson in one place

In the model, each running function has a frame on the stack, and the frame holds its variables. A variable that holds a primitive holds the value itself. A variable that holds an object holds a reference: a way to find the object in the heap. When nothing can find an object any more, the garbage collector may clean it up. The garbage collector is the part of the engine that frees memory you no longer use.

Copying a number makes a separate copyPop out in the code editor (opens in a new tab)JavaScript
let score = 3;
let nextScore = score;
nextScore = 4;
console.log(score, nextScore);

Line 1 puts the number 3 in score. Line 2 copies that 3 into a new variable, nextScore. Line 3 puts 4 in nextScore only. So line 4 prints 3 4. The two variables never share anything.

Two boxes. The score box holds 3. An arrow labelled line 2 copies 3 leads to the nextScore box, which now holds 4 because line 3 put 4 there. The score box still holds 3.3scorestill holds 3line 2 copies 34nextScoreline 3 puts 4 here
Line 2 copies the number 3 into its own box, nextScore. Line 3 puts 4 in that box only. The score box still holds 3, so line 4 prints 3 4.

The stack and the heap, step by step

STEP THROUGH
Real-life analogyYour desk and a storage room

You keep small notes for today's task on your desk. Bigger things go on numbered shelves in a storage room. A note that says "shelf 1" doesn't hold the parcel. It tells you where to find the parcel.

In real life: Your desk, with notes for today’s task
In JavaScript: The stack: one frame for each running function
In real life: A note that says a: 1
In JavaScript: A variable that holds a primitive value
In real life: A parcel on shelf 1 of the storage room
In JavaScript: An object in the heap
In real life: A note that says user: shelf 1
In JavaScript: A variable that holds a reference

Step through the program below. First, watch a number get copied. Then watch two variables point to the same object. Line 3 uses let user, not const, because line 6 gives user a new value, null. A const variable can't get a new value.

Step through the stack and the heap
Step 0 of 8Ready
Your turn: follow the blue line

A replay of the stack-and-heap model, recorded from real JavaScript values. Guess what line 7 prints, then step through each variable and reference.

Running in
  1. script
Next: line 1
let b = a;let user = { name: "Ada" };const other = user;other.name = "Lin";user = null;console.log(other.name);
CallStoreChangeResultBlue line: runs next. Ran: already executed.
Recent returnsNothing yet. Start with the blue line.
Change the name written on line 5.

Picking a name starts the replay again from line 1.

A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.

Look closely at line 5. other.name = "Lin" doesn't change what other holds. JavaScript follows the reference in other to the object, and changes the object's name. The variable user holds the same reference, so user.name would also give "Lin".

After line 5. On the stack side, the user box and the other box each hold the reference #1. Both point to one object in the heap, object #1, whose name is Lin.stack#1user#1otherheapname: "Lin"#1line 5 changed the name
After line 5, user and other each hold the same reference, #1. Both point to one object in the heap. Line 5 changed that one object, so reading the name through either variable gives "Lin".

Line 6 sets user to null. That changes only the variable user. The object is still in the heap, and other still points to it. So line 7 prints Lin.

Copying a value vs copying a reference

COMPARE

People often say, "Primitives are copied by value, and objects are copied by reference." That is close. Here is a clearer rule:

Every variable holds a value, and for an object, that value is a reference.

When you copy a variable, you copy whatever it holds:

  • For a number, you get a second number.
  • For an object, you get a second reference to the same object.

The model is simpler than what engines really do. The table compares the two. Read each row as: "the model shows it like this, but an engine may really do this."

The model compared with what an engine may really do
What kind of valueHow the model shows itWhat an engine may really do
Primitives, like 1, true, or nullThe variable holds the value itself.The engine may keep a small number right inside the variable, keep it in a fast spot inside the processor, or store a number as a small object on the heap. Your code can't tell which.
Objects and arraysThe variable holds a reference to an object in the heap.The engine may move the object to a new spot to save space. A reference is not a memory address you can print.
Closures: functions that remember outer variablesWhen a function finishes, its frame is gone. Variables that an inner function still uses stay available.The engine keeps those variables somewhere that lasts after the call, often on the heap.
StringsStrings act like primitive values.The engine may let equal strings share one stored copy. Your code still sees strings as values that never change.
A useful rule

Giving a variable a new value with = changes only that variable. This is called reassignment. Changing a property, like other.name = "Lin", changes the object itself. This is called mutation. If two variables refer to the same object, both see a mutation.

Spread copies only the outer objectPop out in the code editor (opens in a new tab)JavaScript
const cart = { item: { name: "tea", quantity: 1 } };
const copy = { ...cart };
copy.item.quantity = 2;
console.log(cart.item.quantity);

Line 1 makes a cart object. Its item property holds another object, called a nested object. Line 2 uses spread, ...cart, to make a new outer object called copy. Spread copies the value of each property. The value of item is a reference. So copy.item points to the same nested object as cart.item. Line 3 changes that shared nested object, so line 4 prints 2.

A copy like this is called a shallow copy: only the outer object is new. It works well when you change one top-level property. When the nested objects must be separate too, you need a deep copy, which copies every level.

Parameters get a copy too

STEP THROUGH

A parameter is the variable a function gets for each value you pass in. Function calls follow the same rule as =. The parameter gets a copy of the value you pass. If that value is a reference, the copy points to the same object. Some people call this call by sharing.

A number parameter is a separate copyPop out in the code editor (opens in a new tab)JavaScript
function addTax(price) {
  price = price + 1;
  return price;
}
let price = 3;
console.log(addTax(price), price);

The function's parameter is called price, and so is the variable outside it. They are two separate variables. Line 5 stores 3 in the outside price. Line 6 calls addTax(price), so the parameter gets its own copy of 3. Line 2 changes that copy to 4, and line 3 returns it. The outside price is still 3. So line 6 prints 4 3.

Changing a parameter's object vs pointing it somewhere new
Step 0 of 7Ready
Your turn: follow the blue line

A parameter gets a copy of the value you pass. For an object, that copy is a reference: changing the object shows outside, but pointing the parameter somewhere new does not.

Running in
  1. script
Next: line 7
function rename(person) {  person.name = "Lin";  person = { name: "Grace" };  return person.name;} const result = rename(user);console.log(user.name, result);
CallStoreChangeResultBlue line: runs next. Ran: already executed.
Recent returnsNothing yet. Start with the blue line.
A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.

Line 2 changes object #1 through person. The caller's user points to the same object, so user.name becomes "Lin". Line 3 makes a new object, #2, and points only person at it. The caller's user still points to #1. So line 9 prints Lin Grace.

Inside rename, after line 3. The user box holds #1 and points to object #1, whose name is Lin because line 2 changed it. The person box holds #2 and points to a new object #2, whose name is Grace, made on line 3.#1userline 2 changes thisname: "Lin"#1#2personline 3 makes #2name: "Grace"#2
After line 3, inside rename. Line 2 changed object #1, which user still points to. Line 3 made object #2 and pointed only person at it. So line 9 prints Lin Grace.

Line 2 is the pattern behind many "Why did this function change my object?" bugs.

Reachability: can JavaScript still find the object?

INTERACTIVE

When may the engine throw an object away? It checks whether the object is reachable:

  1. It starts from roots. Roots are places the engine can always see, such as global variables and the variables of functions that are running right now.
  2. It follows every reference from the roots, then every reference from those objects, and so on.
  3. Any object it gets to is reachable, so it must stay.
  4. Any object it can't get to is collectible: the garbage collector is allowed to clean it up.
Real-life analogyA family WhatsApp group

Start with the people in the group. Follow the contacts they shared, then the contacts those people shared. Anyone you can reach this way stays. Anyone you can't reach can be deleted.

In real life: People already in the group
In JavaScript: Roots: global variables and the variables of running functions
In real life: A contact someone shared in a message
In JavaScript: A reference from one object to another
In real life: Someone nobody in the group can reach
In JavaScript: An object no root can reach
In real life: Deleting contacts nobody can reach
In JavaScript: The garbage collector cleaning up
Returning an object keeps it reachablePop out in the code editor (opens in a new tab)JavaScript
function makeProfile(name) {
  return { name };
}
const profile = makeProfile("Asha");
console.log(profile.name);

Line 4 calls makeProfile("Asha"). The function makes an object and returns a reference to it. Line 4 stores that reference in profile. The function has finished, and its frame is gone. But the object is still reachable through profile, so it stays. Line 5 prints Asha.

Reachability: remove the paths one by one
Setting variables to nullPop out in the code editor (opens in a new tab)JavaScript
let globalUser = { profile: { city: "Kochi" } };let modal = { owner: globalUser };let a = { name: "A" };a.friend = { name: "B", friend: a };a = null;modal = null;globalUser = null;
What points to whatRoots reach user and modal

Roots

  • globalUser → user
  • modal → modal

Objects in the heap

  • user object

    reachable

    points to: profile
  • profile object

    reachable

    points to nothing
  • modal object

    reachable

    points to: user
  • object A

    unreachable: can be cleaned up

    points to: b
  • object B

    unreachable: can be cleaned up

    points to: a
Step 1 of 3Roots reach user and modal

Roots are starting points, such as global variables. Any object you can get to by following references from a root is reachable. Line 5 set a to null, so no root reaches A or B any more.

A model of mark-and-sweep. It shows which objects a root can still reach, not when memory is really freed.

Look at objects A and B. Line 4 makes them point to each other. Objects that point to each other in a loop form a cycle. Line 5 sets a to null, so after that no root points to A or B.

Modern garbage collectors use mark-and-sweep. First they mark every object they can reach from the roots. Then they sweep away, or clean up, every object that isn't marked. They never reach A or B, so both can be cleaned up together.

After line 6. Left: the root globalUser points to the user object, which holds profile, so they are kept. Right: objects A and B point to each other, but no root reaches them, so they can be cleaned up.a root reaches itglobalUserrootprofileuserkeptno root reaches themABcan be cleaned up
After line 6, the root globalUser still reaches the user object and its profile, so they stay. A and B point to each other, but no root reaches them, so both can be cleaned up. The modal object, cut on line 6, can be cleaned up too.

An older method, reference counting, counts how many references point to each object. It frees an object when its count reaches zero. In a cycle, A and B each keep the other's count at 1. So reference counting alone would never free them. The later Reference counting & cycles lesson covers this.

Reachable or collectible?
  • window.currentUser points to an object
  • A function makes an object and returns it to the caller, who stores it
  • Two objects point to each other, but no root points to either one
  • An array slot was set to null, and no other reference to the old object exists
  • A Map at the top level of a file still stores an object as a value
  • Only a WeakMap key refers to an object
Try it yourself
0 of 6 correct

Sort each case by whether a root can still reach the object. Collectible means it may be cleaned up, not that it is cleaned up right away.

Choose a category for every card. You can change an answer at any time; Reset clears them all.

Garbage collection basics

Garbage collection means the engine frees memory for you. Your program makes objects as it runs. Every so often, the engine finds objects that are not reachable and reuses their memory. Your code can't see the exact moment an object is freed. So don't write code that depends on that moment.

Many engines also use generational collection. Most objects are needed only for a short time. So the engine checks new objects more often than old ones. Engines may also move the objects that are still in use closer together, to fill the gaps. The engine makes these choices. Your code can't ask for them for one object.

The table explains the main words. The last column shows how to talk about each one without saying something wrong.

Garbage collection words in plain English
WordWhat it meansHow to say it carefully
RootsStarting points the engine can always see: global variables, the variables of running functions, and references the engine keeps itself.If a root can reach an object, the object must stay.
Mark-and-sweepFirst mark every object you can reach from the roots. Then sweep away every object that isn't marked.This is why a cycle that no root reaches can still be cleaned up.
Generational collectionMost objects are needed only briefly, so the engine checks new objects more often than old ones.The engine does this to save time. You can't control it from your code.
CollectibleNo root can reach the object, so it may be cleaned up.Don't say it is cleaned up the moment a variable becomes null.
Finding real memory problems

Your browser's DevTools has a Memory panel. It can take a heap snapshot: a list of every object in memory at that moment. Take two snapshots a while apart and compare them. Objects that keep piling up point to a memory problem. The pictures in this lesson are models. A heap snapshot shows what is really there. The later Finding memory leaks lesson goes deeper.

Where you’ll use this

You'll use this model in everyday frontend work:

  • Fixing an object that changed when you didn't expect it.
  • Choosing between Map and WeakMap.
  • Cleaning up timers and event listeners.
  • Finding out why a web app gets slower after you move between many of its pages.
A Map cache you must clear yourselfPop out in the code editor (opens in a new tab)JavaScript
const seenUsers = new Map();

function rememberUser(user) {
  seenUsers.set(user.id, user);
}

function clearFinishedUsers() {
  seenUsers.clear();
}

A cache is a store of data you keep so you don't have to fetch or build it again. Line 1 makes the cache: a Map at the top level of a file. That Map stays reachable as long as the file is loaded. A Map keeps every value it stores reachable too. So a cache that only grows is a memory leak: memory that stays in use even though you no longer need the data. Line 8 fixes it: seenUsers.clear() removes every entry.

Keep only the newest entriesPop out in the code editor (opens in a new tab)JavaScript
const recentUsers = new Map();
function remember(user) {
  recentUsers.set(user.id, user);
  if (recentUsers.size > 2) recentUsers.delete(recentUsers.keys().next().value);
}
remember({ id: "a" });
remember({ id: "b" });
remember({ id: "c" });
console.log(recentUsers.size);

This version keeps only the two newest users. Line 3 adds a user. On line 4, if there are more than 2 entries, it deletes the oldest key. A Map remembers the order its keys were added, so recentUsers.keys().next().value is the oldest key. The third call, on line 8, adds "c" and deletes "a". So line 9 prints 2. A size limit works well when data is useful for a while but shouldn't grow forever.

A WeakMap for extra data about objectsPop out in the code editor (opens in a new tab)JavaScript
const metadata = new WeakMap();

function rememberElement(element, info) {
  metadata.set(element, info);
}

If the extra data belongs to an object, a WeakMap may be better. A WeakMap uses objects as keys, and it holds them weakly: the key alone doesn't keep the object reachable. When nothing else refers to element, the garbage collector may clean up both the element and its info.

More habits that prevent leaks:

  • A timer or event listener you forget about keeps its function reachable, and everything that function uses. Clear it when you're done.
  • Arrays, Maps, and caches that keep growing need a size limit, deletion, or clearing.
  • A page element you removed from the page stays in memory if a JavaScript variable still points to it. Drop that reference too.
  • A closure, a function that remembers outer variables, can keep large data alive. Keep what it remembers small, or let go of it when the work is done.
  • A const object variable can't get a new value with =, but you can still change the object's properties.
  • Clean up the same thing you set up: remove a listener with the same function you added, and clear a timer you started.

Common misconceptions

“Primitives always live on the stack.”

That is a simplification. JavaScript promises that primitives are copied as values. Where they are really stored is up to the engine.

“An object variable contains the whole object.”

It holds a reference to the object. Copying the variable copies the reference, not the object.

“const makes an object unchangeable.”

const only stops the variable from getting a new value with =. You can still change the object's properties, and the objects inside it.

“Setting a variable to null frees the object.”

It removes one path to the object. If a root can still reach the object another way, it stays. Even when nothing reaches it, you can't tell when it is cleaned up.

“Cycles always leak.”

Mark-and-sweep can clean up a cycle that no root reaches. Leaks happen when something reachable keeps growing.

“Strings must be stored on the stack.”

In JavaScript, strings are primitive values that never change. Where the engine stores them, and whether equal strings share storage, is up to the engine.

“A WeakMap makes values disappear right away.”

A WeakMap only stops its keys from keeping objects alive. It doesn't change when cleanup happens, and you still can't predict that moment.

Practice: predict and fix memory behavior

5 EXERCISES
Exercise 1 · Warm-upPredict the shared object

Trace the code and type the exact value it prints.

Starter codePop out in the code editor (opens in a new tab)JavaScript
let user = { name: "Ada" };
let other = user;
other.name = "Lin";
user = null;
console.log(other.name);

Answer, then press Check. Spacing and letter case don’t matter.

    Exercise 2 · PracticeReachability check

    In the reachability playground, look at the last step, after both modal and globalUser are null. Is the nested profile object reachable or collectible?

    Answer, then press Check. Spacing and letter case don’t matter.

      Exercise 3 · PracticeExplain the cycle

      Two objects point only to each other. Every outside reference to them is gone. Can they be cleaned up?

      Answer, then press Check. Spacing and letter case don’t matter.

        Exercise 4 · ChallengeFix a growing cache

        A Map at the top level of a file grows every time a user opens a profile. Name one fix.

        Starter codePop out in the code editor (opens in a new tab)JavaScript
        const seenUsers = new Map();
        
        function rememberUser(user) {
          seenUsers.set(user.id, user);
        }
        
        function clearFinishedUsers() {
          seenUsers.clear();
        }

        Answer, then press Check. Spacing and letter case don’t matter.

          Exercise 5 · ChallengeDon't change the caller's object

          A function should add a role to a person without changing the object it receives. What kind of value should it return?

          Starter codePop out in the code editor (opens in a new tab)JavaScript
          function label(person) {
            return { ...person, role: "admin" };
          }

          Answer, then press Check. Spacing and letter case don’t matter.

            Quiz: check your memory model

            7 QUESTIONS
            Lesson quiz · 7 questionsScore: first tries count
            1. Question 1 of 7In this lesson, what is the stack-and-heap picture?

              Choose an answer to see the explanation.

            2. Question 2 of 7What does this print?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              let a = 1;
              let b = a;
              b = 2;
              console.log(a);

              Choose an answer to see the explanation.

            3. Question 3 of 7Two variables point to one object. What does this print?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              const user = { name: "Ada" };
              const other = user;
              other.name = "Lin";
              console.log(user.name);

              Choose an answer to see the explanation.

            4. Question 4 of 7A function gets an object as its parameter person. Which line changes the caller's object?

              Choose an answer to see the explanation.

            5. Question 5 of 7Two objects point only to each other, and no root reaches either one. What happens to them?

              Choose an answer to see the explanation.

            6. Question 6 of 7You want to store extra data about an object, but the data shouldn't keep the object alive. Which store fits?

              Choose an answer to see the explanation.

            7. Question 7 of 7Which sentence about garbage collection is true?

              Choose an answer to see the explanation.

            Key takeaways

            • The stack-and-heap picture is a model for thinking, not an exact map of real memory.
            • Copying a primitive makes a separate value. Copying an object variable copies a reference to the same object.
            • Changing a property changes the object. Using = on a variable changes only that variable.
            • An object stays while a root can reach it. A cycle that no root reaches can still be cleaned up.
            • The engine cleans up memory on its own schedule. Your code shouldn't depend on when.

            Remember the one-liner.
            An object stays alive while a root can still reach it. Copying an object variable copies the way to find the object, not the object.

            Up next: Scope & closures.

            CompleteFrontend Clear concepts. Working examples.