Suggest an editImprove this articleRefine the answer for “What is the "call stack" in the context of memory?”. Your changes go to moderation before they’re published.Approval requiredContentWhat you’re changing🇺🇸EN🇺🇦UAPreviewTitle (EN)Short answer (EN)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. **Key point:** a stack overflow happens when recursion is too deep or local data is too large.Shown above the full answer for quick recall.Answer (EN)Image## 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.For the reviewerNote to the moderator (optional)Visible only to the moderator. Helps review go faster.