Lesson Outcome:
Describe the call stack and heap memory model and how primitives and reference types are stored and accessed during execution.
Welcome back. In our previous lesson, we demystified the V8 engine's compilation pipeline, tracing how your JavaScript is parsed, interpreted into bytecode by Ignition, and then JIT-compiled into blazing-fast machine code by TurboFan. We now understand how the engine processes instructions. This lesson answers the next critical question: where does the data that this code operates on actually live? We will explore the two fundamental memory regions in the JavaScript runtime—the Call Stack and the Heap—and see how JavaScript’s core data types are stored and accessed during execution.
Why This Matters at a Senior Level
[Often asked]
Understanding memory allocation isn't just academic; it's the bedrock of writing performant, bug-free code at scale. A mid-level developer knows that changing an object in one place can affect it elsewhere. A senior engineer knows why this happens (reference sharing on the heap), understands the performance cost of object allocation versus stack variables, and can diagnose subtle bugs caused by unintended object mutations. In an interview, being able to articulate the difference between the behavioral model of Stack/Heap and the actual V8 implementation (like Smi optimization) is a massive differentiator. It shows you don't just use the language; you understand the machine it runs on.
Core Concepts: Values vs. References
Let's start with a classic piece of JavaScript behavior that trips up many developers. Consider these two snippets:
// Snippet 1: Primitives
let name1 = 'Alice';
let name2 = name1; // Copy the value
name1 = 'Bob'; // Re-assign name1
console.log('name1:', name1); // -> 'Bob'
console.log('name2:', name2); // -> 'Alice'
This first example feels intuitive. name2 gets a copy of name1's value, and changing name1 later doesn't affect name2.
Now, let's look at a similar pattern with an object:
// Snippet 2: Objects
let person1 = { name: 'Alice' };
let person2 = person1; // Copy the reference
person1.name = 'Bob'; // Mutate the object
console.log('person1.name:', person1.name); // -> 'Bob'
console.log('person2.name:', person2.name); // -> 'Bob'
Wait. We changed person1, but person2 changed as well. Why? The answer lies in how JavaScript organizes memory into two distinct areas: the Call Stack and the Heap.
The Two Arenas of Memory
- The Call Stack: This is a region of memory that operates on a Last-In, First-Out (LIFO) basis. Its primary job is to keep track of function calls. When you call a function, a "stack frame" is pushed onto the top of the stack. This frame holds the function's arguments, local variables, and the return address. When the function returns, its frame is popped off the stack. Access to the stack is extremely fast, but its size is limited.
- The Heap: This is a much larger, less-organized region of memory. It's where JavaScript stores objects—and anything that doesn't have a fixed size at compile time. Memory allocation on the heap is more complex and slower than on the stack, and this is where the garbage collector does its work.
The behavior we saw earlier is explained by which region is used for which data type.
Primitives vs. Reference Types: The Simplified Model
This is the foundational model that explains 95% of the behavior you'll encounter.
- Primitive Types are stored directly on the stack. This includes
string,number,boolean,null,undefined,symbol, andbigint. Because they have a known, fixed size, the engine can allocate space for them directly in the stack frame. When you assign a primitive from one variable to another (e.g.,let b = a), the value itself is copied. - Reference Types (i.e., Objects, including Arrays and Functions) are stored on the heap. The variable on the stack does not hold the object itself. Instead, it holds a reference—a pointer or memory address—to the object's location in the heap.
Let's watch a couple of short videos that provide excellent visual explanations of this model.
Video 1
This video from Traversy Media, "JavaScript Under The Hood [4] - Memory Storage," gives a concise overview of the Stack and Heap.
Guidance 5 mins
Watch from , which clearly explains and diagrams how primitives are placed on the stack, how objects are placed on the heap with a reference on the stack, and how this leads to the different behaviors when copying and modifying them.
Video 2
Academind's video, "JavaScript - Reference vs Primitive Values/ Types," offers another great perspective with clear code examples.
Guidance 5 mins
Focus on the segment from . It uses diagrams to illustrate how the stack stores primitives directly, while for objects, it stores a pointer that references a location on the heap. This directly explains why mutating an object via one variable is visible via another.
The image below provides a perfect visual summary of our code example.

When newPerson = person is executed, only the reference (the red line) is copied, not the object itself. Both person and newPerson variables on the stack end up pointing to the exact same object on the heap.
Mental Model: A Locker Room and a Warehouse
To make this stick, think of memory like this:
- The Call Stack is a locker room. Each locker (
variable) is small and has a fixed size. When you store a primitive value like the number42, you put42directly inside the locker. If a friend wants a copy, they get their own locker and you put a new42inside it. Your lockers are separate. - The Heap is a giant, connected warehouse. It's for big, bulky items (objects). You can't fit a car (
object) in your locker. So, you park the car in the warehouse at a specific parking spot number (a memory address). Inside your locker, you just keep a small keycard with the parking spot number written on it (the reference). If a friend wants to "copy" your car, you don't give them a new car. You just give them a copy of your keycard. Now you both have keycards pointing to the same car. If you go and paint the car red, your friend will see a red car too.
Refining the Model: How V8 Actually Stores Values
The locker/warehouse model explains the behavior perfectly. As a senior engineer, it's crucial to also understand the underlying implementation, which is more nuanced.
Reading 1
Guidance 10 minutes
First, read the and the section on mutability. This establishes that the official ECMAScript spec defines behavior (mutability), not storage location.
Next, and most importantly, read the section . Pay close attention to Pointer Tagging, the concept of Smis (Small Integers), and the fact that strings are stored on the heap. This is a critical distinction from the simplified model. Finally, the section ties it all together.
https://33jsconcepts.com/concepts/primitives-objectsAs you've just read, the big reveal is:
- Pointer Tagging: V8 uses a clever trick. For a given "word" of memory (e.g., 32 or 64 bits), it uses the last bit as a tag.
- Smi (Small Integers): If the tag bit is
0, the engine knows the rest of the bits represent an integer. This is the only value type that is truly stored "on the stack" without a heap allocation. It's incredibly fast. - Heap Pointers: If the tag bit is
1, the engine knows the rest of the bits are a memory address pointing to something on the heap. This "something" includes all objects, arrays, functions, large numbers, and even strings.
The common misconception that strings are on the stack is incorrect because strings are dynamically sized. A fixed-size stack slot can't hold a string that could be one character or one million characters long.
So, the Stack/Heap division is a behavioral mental model. The Smi/Heap-Pointer division is V8's optimized implementation.
Reading 2
This article from the Dashlane Engineering blog provides a more technical confirmation of these concepts by inspecting a debug build of V8.
Guidance 8 minutes
Skim the sections on to reinforce your understanding of tagging, SMI, and HeapObject. Then, review the sections covering how V8 stores and the summary of .
How Is Data Stored in V8 JS Engine Memory? | DashlaneExplore the inner workings of the V8 JavaScript engine, from data types to memory allocation. Gain insights into tagged pointers, hidden classes, and optimization techniques for enhanced JavaScript performance.
Common Misconceptions
- Myth: "Primitives are stored on the stack, objects on the heap." Reality: This is a behavioral model. In V8's implementation, only small integers (Smis) are stored directly. Other primitives like strings and large numbers are allocated on the heap, though their immutable behavior makes them act like stack values.
- Myth: "JavaScript passes objects by reference." Reality: JavaScript uses a strategy called "call by sharing." When you pass an object to a function, a copy of the reference is passed. This is why you can mutate the original object's properties, but if you try to reassign the entire variable to a new object inside the function, it does not affect the original variable outside the function. True "pass by reference" would allow the external variable to be reassigned.
- Myth: "
constmakes an object immutable." Reality:constonly prevents the variable from being reassigned to a different reference. It does absolutely nothing to prevent the object itself from being mutated. You can still change properties on aconstobject or push items into aconstarray.
Senior Insight
A mid-level developer correctly predicts that changing a property on a shared object will be reflected everywhere. They use this knowledge to manage state.
A senior engineer, however, thinks about the second-order effects. They understand that every time you create an object ({} or []), you are causing a heap allocation, which has a performance cost and puts pressure on the garbage collector. They know that passing a giant object to 10 functions is cheap (you're just copying a small pointer 10 times), but creating 10 deep clones of that object is expensive. When reviewing code like const newArray = oldArray.map(...), they see not just a data transformation, but a heap allocation for newArray and for every new object created inside the map. This understanding allows them to reason about memory pressure and performance in a way that goes far beyond just understanding reference semantics.
Production War Story
A frontend team was building a complex dashboard with multiple charts. They created a global default configuration object for the charting library:
export const defaultChartOptions = { animation: true, color: 'blue', axis: { labels: { enabled: true } } };
One team working on a specific "real-time" chart needed to disable animations for performance. They wrote this code:
import { defaultChartOptions } from './config';const realTimeOptions = defaultChartOptions;realTimeOptions.animation = false;
Suddenly, bug reports flooded in: all charts across the entire application had stopped animating. Because JavaScript modules are singletons, every part of the app that imported defaultChartOptions got a reference to the same object on the heap. The team working on the real-time chart had inadvertently mutated this single, shared object, affecting every other component that used it. The fix was to export a factory function, export const createDefaultOptions = () => ({...});, ensuring every consumer gets a new copy of the options object on the heap, preventing this kind of cross-component state pollution.
Conclusion
Today, we've built a solid mental model for how JavaScript manages memory. We've seen that the runtime is divided into the fast, orderly Call Stack and the large, flexible Heap. This division explains the fundamental behavioral difference between primitives (which are copied) and objects (which are referenced).
Key Takeaways:
- Call Stack: Manages function execution (execution contexts). Stores primitive values and references to objects. It's fast, ordered (LIFO), and has a limited size.
- Heap: Stores objects, arrays, and functions. It's a large, unstructured memory pool. Allocation is more complex and managed by the garbage collector.
- Value vs. Reference: Assigning a primitive copies the value. Assigning an object copies the reference (the "address"), leading to shared state.
- The V8 Nuance: While the Stack/Heap model explains behavior, V8's implementation is more optimized. It uses pointer tagging to store small integers (Smis) directly and uses heap pointers for everything else, including strings.
In our previous lesson, we saw how code is executed. Now we know where its data lives. In our next lesson, we will connect these two concepts and explore Hidden Classes and Inline Caches. You'll discover the clever optimizations V8 uses to make accessing properties on heap objects incredibly fast, despite JavaScript's dynamic nature.





