Skip to main content

What is the "call stack" in the context of memory?

Short answer

The call stack is a region of a process's memory where frames of active functions are stored in layers (LIFO order): the return address, arguments, local variables, and saved registers. It is very fast, managed automatically on function entry/exit, has a limited size, and is created separately for each thread. A stack overflow happens when recursion is too deep or local data is too large.

Detailed breakdown

What is stored in the stack

  • Call frames (stack frames) - one frame per active function.
  • The return address - where to resume execution after a return.
  • The function's arguments and its local variables (those that do not "escape" beyond the function).
  • Saved registers and the frame pointer (FP) - for restoring context.
  • LIFO order: the last one called is the first one finished.
  • Automatic lifetime: memory is allocated/freed on function entry/exit.
  • Limited size (usually megabytes), fast access, and no explicit GC for the stack.
  • Each thread has its own stack; SP (stack pointer) and often FP (frame/base pointer) are used.

Stack vs. heap

  • Stack: fast, LIFO, automatic, limited, local data and arguments, function frames, no explicit freeing needed.
  • Heap: flexible, arbitrary object lifetimes, larger size, allocation/freeing is more expensive; managed manually (C/C++) or by a GC (JS, Java).

How a call and return happen

  1. Passing arguments to the called function (via the stack/registers, depending on the ABI).
  2. Saving the return address and the needed registers, moving the stack pointer (SP) - "pushing" the frame.
  3. Running the function body, working with local variables.
  4. On return: restoring registers, collapsing the frame (SP moves back), jumping to the return address.
+----------------------------+ high addresses | frame: main | | locals, args | | return addr -> runtime | +----------------------------+ | frame: g() | | saved FP, locals, args | | return addr -> main | +----------------------------+ | frame: f() | | saved FP, locals, args | | return addr -> g | +----------------------------+ low addresses ^ SP (stack pointer) - points to the top of the stack FP (frame pointer) - points to the base of the current frame

Code examples

C: local variables, recursion, and a stack overflow

#include <stdio.h> int f(int n) { int local = n; // a local variable on the stack if (n == 0) return local; return f(n - 1) + 1; // every call creates a new frame } int main() { // A large object on the stack - a risk of overflow (don't do this) // int big[10 * 1024 * 1024]; // ~40 MB on 64-bit - a likely crash // Deep recursion can also overflow the stack printf("%d\n", f(1000000)); // it will crash at some point (stack overflow) return 0; } // IMPORTANT: you must not return the address of a local variable // int* bad() { // int x = 42; // x lives only while the function's frame is on the stack // return &x; // WRONG: the frame is destroyed after return // }

In C, local variables live until the function returns. Large local arrays or deep recursion easily lead to a stack overflow. Use the heap (malloc/new) for large buffers.

JavaScript: the call stack and asynchrony

// A synchronous stack and a trace function a() { b(); } function b() { c(); } function c() { // In most JS engines, Error().stack shows the current stack console.log(String(new Error().stack)); } a(); // Asynchrony: callbacks do not continue the current stack console.log('start'); setTimeout(() => console.log('timeout callback (a new stack)'), 0); Promise.resolve().then(() => console.log('microtask (after the current stack)')); console.log('end'); // A stack overflow in JS function recurse(i) { return recurse(i + 1); } try { recurse(0); } catch (e) { console.error(e.name + ': ' + e.message); // RangeError: Maximum call stack size exceeded }

In JS the stack is managed by the engine: every synchronous call adds a frame, a return removes it. Asynchronous callbacks run later, once the current stack is empty - that is already a new stack, so they have no "continuation" of the stack. The overflow error occurs with recursion that is too deep (often infinite).

Exceptions and "stack unwinding"

When an exception is thrown (for example, throw), the executed but unfinished frames are removed one by one up to the handler (catch) - this is called stack unwinding. At each step, finalizers run: finally blocks (JS), destructors (C++/Rust drop). If no handler is found, the program terminates with an error and prints a stack trace.

Multithreading and the web

  • Each OS thread has its own stack with a fixed initial size (often 1-8 MB), which can grow up to a limit.
  • In a browser, the main UI thread and each Web Worker have their own stack. Event/timer callbacks run on an "empty" stack, once the event loop is ready to handle them.
  • Asynchronous boundaries (timers, I/O, promises) break the continuity of the stack: the code that runs "next" is already in a new frame and often has a new trace.

Optimizations and nuances

  • Tail-call optimization (TCO): in theory it avoids growing the stack for tail recursion, but in JS it is rarely implemented in practice.
  • Inlining and omitting the frame pointer: compilers/engines can remove frames or merge them, which affects stack traces while debugging.
  • Escape analysis: if an object does not "escape" beyond the function, some languages/compilers place it on the stack (Go, in JIT optimizations). In JS, closures usually push data onto the heap.
  • Security: guard pages and canary values help detect stack overflows at the OS/runtime level.

Practical recommendations

  • Avoid deep/infinite recursion in JS: prefer iteration, or rewrite as tail recursion only if TCO is guaranteed (usually it is not).
  • Do not place large amounts of data on the stack; use the heap for arrays/buffers of significant size.
  • Understand the stack boundaries under asynchrony: errors and traces often "break off" at an await/then boundary; add context manually if needed.
  • Use stack traces in logs for diagnostics; in the browser and Node.js, rely on Error.stack and source maps.

Short Answer

Interview ready
Premium

A concise answer to help you respond confidently on this topic during an interview.