A call stack is a runtime Stack used by a Programming Language to keep track of active Functions.
Each time a function is called, the runtime pushes a stack frame onto the call stack. A stack frame usually stores the function’s arguments, local variables, return address, and bookkeeping needed to resume the caller. When the function returns, its frame is popped from the stack.
The call stack follows last in, first out order: the most recently called function must finish before its caller can continue.
flowchart TB subgraph Calls["Call and return order"] direction LR MainCall["main()"] Func1Call["func1()"] Func2Call["func2()"] MainCall -->|"calls"| Func1Call Func1Call -->|"calls"| Func2Call Func2Call -->|"returns to"| Func1Call Func1Call -->|"returns to"| MainCall end subgraph Stack["Call stack while func2() is running"] direction TB F2["top: func2()<br/>locals + return to func1()"] F1["func1()<br/>locals + return to main()"] F0["main()<br/>locals"] end
In a nested call such as main() → func1() → func2(), func2() is at the top of the call stack. It returns to func1(), then func1() returns to main().
If calls keep being added without returning, such as with Recursion that has no reachable base case, the call stack can run out of space. This is called a stack overflow.
Differences between languages
Most major languages have the same basic call stack model: a function call adds a frame, and returning removes it. The differences are usually about who manages the stack, how errors are reported, and whether the language can avoid growing the stack in some cases.
| Language | Typical behaviour |
|---|---|
| C Language / C++ | The native machine stack is used directly. Stack frames are low-level and efficient, but stack overflows may crash the program or cause undefined behaviour rather than producing a tidy language error. |
| Java | Each thread has its own call stack. Deep or infinite recursion usually raises StackOverflowError. Stack traces are a standard debugging tool. |
| Python | Uses interpreter-managed frames on top of the native stack. Python has a recursion limit and usually raises RecursionError before the process stack is exhausted. |
| Javascript | Uses the engine’s call stack. Deep recursion usually raises an error such as RangeError: Maximum call stack size exceeded. Asynchronous callbacks, promises, and async/await do not keep one long synchronous call stack while waiting. |
| Golang | Goroutines start with small stacks that can grow and shrink. This makes many lightweight concurrent call stacks practical. |
| Rust | Uses the native stack like C and C++, but memory safety rules prevent many stack-related bugs around invalid references. Stack overflow handling depends on platform and build/runtime settings. |
| Erlang / Elixir | Each lightweight process has its own stack and heap managed by the BEAM virtual machine. Recursion is common, and tail-recursive functions can run without growing the stack. |
| Haskell | Lazy evaluation means the runtime stack may represent pending computations rather than simple eager function calls. Space leaks can happen when too many unevaluated thunks build up. |
Some languages and runtimes support tail call optimisation. With tail calls, a function call in final position can reuse the current stack frame instead of pushing a new one. This is important in functional programming because it allows some recursive code to run like a loop.