Base CS from zero
The stack trace
When a program crashes, it does not just say “something went wrong.” It prints a block of text — usually several lines, each naming a function and a line number. New programmers often skip past it as noise. It is the opposite of noise: it is the single most useful piece of information the runtime can give you.
That block is the stack trace. It is a photograph of the call stack — the exact stack of frames from Unit 08 — taken at the instant the exception was raised. Once you can read it, a crash stops being a mystery and becomes a precise statement: this function threw, and here is the exact chain of calls that led to it.
After this lesson you can define a stack trace as the snapshot of the call stack at the moment an exception is raised, identify the topmost frame as the throw site and the bottom frame as the program entry, read a trace top-down to reconstruct the call chain, and trace how an exception propagates frame by frame as the runtime unwinds.
Recall the call stack from Unit 08: a stack of frames, one per active function call, the most recently called function on top, the program entry at the bottom. At any instant the stack holds exactly the frames of all currently active calls.
A stack trace is what you get when you write that stack down. The moment an exception is raised, the runtime records the call stack as it is right then: a list of frames, one line per frame. Each line names the function and the line number where that function was currently executing — for the top frame, the line that raised the exception; for every frame below, the line where it called the function above it.
The trace is printed top frame first:
- The top line is the frame that raised the exception — the throw site. This is where the error actually surfaced.
- Each line below is the caller of the line above it, going down toward the program
entry. The bottom line is the outermost call — typically
mainor the entry point.
So reading top-down, a trace answers two questions at once: what threw (top line) and through what chain of calls the program reached that point (every line below). It is the call stack from Unit 08, frozen and printed.
The program traced below has the call chain main → loadUser → getAge → divide.
divide raises an exception, and you will watch the trace build up as the runtime
unwinds.
1
function divide(a: number, b: number): number {
2
if (b === 0) throw new Error("divide by zero");
3
return a / b;
4
}
5
6
function getAge(birthYears: number, people: number): number {
7
return divide(birthYears, people);
8
}
9
10
function loadUser(): number {
11
return getAge(60, 0); // people = 0 — will cause the throw
12
}
13
14
function main(): void {
15
loadUser();
16
}
17
18
main();
- L2 Throw site: divide detects b === 0 and raises the exception. This is the TOP line of the trace.
- L7 getAge called divide here — this line appears in the trace below divide.
- L11 loadUser called getAge with people = 0 — this line appears below getAge.
- L15 main called loadUser here — this line appears below loadUser.
- L18 Program entry: main() called. The BOTTOM line of the trace.
The stack trace printed when this program crashes looks like this — top frame first:
1
Error: divide by zero
2
at divide (app.ts:2)
3
at getAge (app.ts:7)
4
at loadUser (app.ts:11)
5
at main (app.ts:15)
- L1 The exception's message — the error description passed to new Error(...).
- L2 TOP frame: divide, line 2 — the throw site. This is where the exception was raised.
- L3 getAge, line 7 — getAge was waiting at line 7, the call to divide.
- L4 loadUser, line 11 — loadUser was waiting at line 11, the call to getAge.
- L5 BOTTOM frame: main, line 15 — the program entry's call to loadUser.
Step through the crash. The cells are the call stack, bottom-to-top (oldest at left). Watch the frames push as calls nest, then the exception propagate as the runtime unwinds — each popped frame is one line the trace records.
1
function divide(a, b) {
2
if (b === 0) throw new Error('divide by zero');
3
return a / b;
4
}
5
function getAge(birthYears, people) {
6
return divide(birthYears, people);
7
}
8
function loadUser() {
9
return getAge(60, 0);
10
}
11
function main() {
12
loadUser();
13
}
14
main();
Why this works
Why is the trace recorded at the throw, not at the crash? Because the call stack is destroyed by unwinding. By the time the program actually crashes, every frame has been popped — the stack is empty. If the runtime waited until then to look, there would be nothing left to describe. So it captures the snapshot the instant the exception is raised, while all the frames are still present, and then prints that captured snapshot when the unwind finishes with no handler.
Common mistake
A common mistake is to read the trace bottom-up and blame main, because main is the
familiar name at the bottom. The bottom frame is just the program entry — it is in every
trace and almost never the culprit. The throw site is the top line. Start your reading
there: that function, at that line number, is where the error surfaced.
A stack trace has 4 lines: divide, getAge, loadUser, main (top to bottom). Which line number (counting the top line as 1) names the throw site — the function that raised the exception?
Same 4-line trace: divide, getAge, loadUser, main (top to bottom). Counting the top line as 1, which line names the program entry?
main calls loadUser calls getAge calls divide. divide throws. The stack trace captured at the throw has how many frames?
No function in the chain has a handler. The runtime unwinds. How many frames does it pop before the stack is empty and the program crashes?
In a trace, the line 'at getAge (app.ts:7)' sits directly below 'at divide'. Line 7 of getAge is which event — type 1 if it is where getAge called divide, 2 if it is where getAge raised its own exception.
What is a stack trace, and how do you read it?
A stack trace is the call stack from Unit 08, frozen and written down at the instant an exception is raised. It is a list of frames, one line each, naming a function and the line it was executing. It is printed top frame first: the top line is the throw site — the function and line where the exception was raised — and each line below is the caller of the line above it, down to the program entry at the bottom. The runtime captures this snapshot at the throw, while the frames still exist, because unwinding then pops every frame and empties the stack. To read a trace, start at the top: that line tells you exactly what threw and where; the lines below tell you the chain of calls that got the program there.