Base CS from zero
The call stack
You know that a CALL instruction saves a return address and jumps to a function. But
where does it save the return address? And what happens when that function itself calls
another function — do return addresses pile up somewhere? Where do local variables live
while the function is running?
The answer is the call stack: a region of memory that the runtime uses as a last-in-first-out structure. Every call pushes a block of data called a stack frame onto the stack; every return pops the topmost frame back off. The frames pile up as calls nest, and unwind in reverse order as functions return. After this lesson you will be able to hold the exact state of the stack in your head at any point during a program’s run.
After this lesson you can describe what a stack frame contains (return address and local variables), explain what “pushing a frame” and “popping a frame” mean, trace the stack state through a sequence of nested calls and returns, and state why the call stack is LIFO.
The call stack is the stack region of the address space (introduced in Unit 02, lesson 04). Recall that the stack region grows and shrinks automatically as functions run. Each time a function is called, the runtime reserves a block of cells for that function — this block is the stack frame (also called an activation record). The frame holds:
- The return address — where the program counter should go when this function returns.
- The function’s local variables — every variable declared inside the function body.
“Pushing a frame” means the runtime places a new frame on top of the stack (the stack pointer moves to reserve new space). “Popping a frame” means the runtime removes the topmost frame (the stack pointer moves back), making those cells available for reuse.
The call stack is LIFO: the function called most recently is always the first to finish. Its frame sits on top and is popped first. The frame below it belongs to the caller, which resumes after the pop. This LIFO order is enforced by how functions nest: the most recently called function must finish before its caller can continue.
The program to trace below has three functions: main calls outer, which calls
inner. All three frames will appear on the stack at the same time before unwinding.
1
function inner(): void {
2
let c = 3; // inner's only local
3
}
4
5
function outer(): void {
6
let b = 2; // outer's only local
7
inner(); // call inner — pushes inner's frame
8
}
9
10
function main(): void {
11
let a = 1; // main's only local
12
outer(); // call outer — pushes outer's frame
13
}
14
15
main(); // program entry — pushes main's frame
- L2 c lives in inner's stack frame — it does not exist until inner is called
- L6 b lives in outer's stack frame
- L7 CALL inner: saves return address (line 8), pushes inner's frame
- L11 a lives in main's stack frame
- L12 CALL outer: saves return address (line 13), pushes outer's frame
- L15 CALL main: starts the whole chain — pushes main's frame
Step through main() call-by-call. Each cell represents one stack frame. Frames are
shown bottom-to-top (oldest at left, newest at right).
1
function inner(): void {
2
let c = 3;
3
}
4
5
function outer(): void {
6
let b = 2;
7
inner();
8
}
9
10
function main(): void {
11
let a = 1;
12
outer();
13
}
14
15
main();
Edge cases
Stack overflow. The stack region has a finite size (typically 1–8 MB on modern systems). If calls nest deeply enough — most commonly through unbounded recursion — the stack runs out of space. When the stack pointer would move past the end of the stack region, the OS sends a signal that terminates the program with a “stack overflow” error. This is not a logic error in the algorithm; it is a physical limit of the reserved address range. You will see how this happens with recursion in lesson 05.
main calls outer; outer calls inner. While inner is executing, how many stack frames are on the stack?
inner returns. How many stack frames are now on the stack?
A stack frame holds the return address and the function's local variables. Function f declares 3 local variables. How many pieces of data does f's frame hold at minimum (return address + locals)?
The call stack is LIFO. If main called outer which called inner, which frame is removed first when functions start returning?
outer calls inner. inner's return address points to the instruction right after the inner() call in outer. If that instruction is at line 8, what line does the program counter jump to when inner returns?
What does a stack frame contain, and when is it pushed and popped?
The call stack is the stack region of the address space managed in LIFO order. When a function is called, the runtime pushes a stack frame onto the stack: a block of cells that holds the return address and all of the function’s local variables. When the function returns, the runtime pops that frame — those cells are freed immediately. At any point during execution, the stack holds exactly the frames of all currently active functions, from the outermost caller at the bottom to the most recently called function at the top. Frames are added and removed in strict last-in- first-out order because the most recently called function must finish before its caller can continue. If calls nest too deeply, the stack region runs out of space and the program crashes with a stack overflow.