Page 5: The Call Stack vs. The Heap: Value Lifetimes and Reference Pointers

Passing an object or array to a function passes a reference pointer to the same heap object—meaning mutations inside the function affect the caller’s object unless you create a defensive copy.

When your program runs, the operating system and language runtime divide its working memory into two primary regions with very different allocation rules: the Call Stack and the Heap. Understanding the distinction between these two memory regions explains two of the most common bugs in software engineering: accidental mutation of shared objects and stack overflow crashes.

The Call Stack is a tightly structured Last-In, First-Out (LIFO) region that tracks active function executions. Whenever a function is called, a new block of memory called a stack frame is pushed onto the top of the stack to hold its parameters, local primitive variables, and the return address. When the function returns, its stack frame is popped instantaneously by moving the stack pointer. The Heap, by contrast, is a large pool of dynamically allocated memory used for objects, arrays, and closures whose size is dynamic or whose lifetime outlasts a single function call.

5.1 Stack Frames and LIFO Execution Order

Suppose function `main()` calls `calculateTotal()`, which in turn calls `applyTax()`. As each call occurs, the runtime pushes a frame onto the Call Stack: `[main] -> [calculateTotal] -> [applyTax]`. While `applyTax()` is executing, the variables inside `calculateTotal()` remain safely preserved in the frame directly beneath it.

Once `applyTax()` finishes and returns a number, its frame is popped off the top of the stack, and execution resumes at the exact instruction inside `calculateTotal()` where `applyTax()` was invoked. Because stack allocation only moves a single pointer up and down, it is extraordinarily fast—but each thread’s stack has a strict size limit (typically a few megabytes).

5.2 Heap Objects and Reference Semantics

Dynamic structures like objects (`{ name: "Ada" }`) and arrays (`[10, 20, 30]`) can grow, shrink, or be shared across multiple parts of an application. Therefore, the runtime allocates them on the Heap and stores a compact memory address (a reference pointer) in the local variable.

When you assign `const b = a` for a primitive number (`let a = 42`), `b` receives an independent copy of `42`. When you assign `const objB = objA` for an object, `objB` receives a copy of the reference address pointing to the exact same object on the Heap.