← Fundamentals
Guided lesson 5 questions · 6 languages

Tracing a small program

One row per step: the next line, every value, and the output so far.

To trace a program, keep a table with one row per step: the line that runs next, the value of every variable, and the output so far. Predict each row before you check it. In a stepper, the highlighted line is the next one to run; it has not run yet.

Before you begin

Why this matters

Most bugs in small programs are a value that is not what you thought at one step. If you write down every step, you find that step instead of guessing. The same table works for straight lines, loops, and calls.

What this helps you reason about

  • Predict, row by row, what each variable holds and what has been printed.
  • Read the stepper correctly: the highlighted line is the next one, not the last one.
  • Find the iteration where a loop goes one step too far or stops one step too early.

Reading a program line by line in your head works for a few lines. After that, values get lost. This lesson shows a written way to follow a TypeScript program: a table with one row per step, filled before you run anything and then checked with the stepper.

Already know this? The short version
  • A trace table has one row per step: the next line, each variable’s value, and the output so far. Fill it before you run, then compare.
  • In the stepper, the highlighted line has not run yet; its effect shows at the next stop. A let that has not run shows as not initialized yet.
  • A while loop gets rows for every iteration and for every check of its condition, including the last, false one.
  • A call adds a frame on top of the stack column; the caller’s line waits and gets the result when the call returns.

Go to the final check· Go to the practice tickets

Words in this lesson (7)
trace table
A table you fill by hand to follow a run. It has one row for each step, with the next line to run, the value of each variable, and the output so far.
variable
A name that holds a value. let and const declare variables; a let variable can be given a new value later, and a const variable cannot.
assignment
Code such as marks = 9 that gives a variable a new value. The right side is computed first, with the values variables hold at that moment; then the variable on the left holds the result.
debugger
A tool that runs a program and pauses it at chosen places, so you can look at the values it holds. Each pause is a stop.
iteration
One run of the block of a loop.
frame
The record of one function call that has not finished: its parameters and local variables. It is created when the call starts and removed when the call returns.
call stack
The frames of all calls that have not finished yet, with the newest on top. A new call adds a frame on top, and a return removes it. Only the newest call runs; each call below it waits for the call it made.

Examples are focused snippets; imports, enclosing functions, or classes may be omitted. C examples use C11; Haskell examples use Haskell 2010. Expand the notes for language-specific depth.

01 / Table

How do you write down a trace?

To trace a program is to follow it by hand, one step at a time, and write down what each step does. A trace table keeps the steps in order. It has one row for each step. Its columns are the line that runs next, the value of each variable, and the output so far.

Fill each row from the row above it. Read the next line, work out what it does with the values in your row, and write the result as a new row. A variable that has no value yet gets a dash. pupils = pupils + 4 reads the value pupils holds now, adds 4, and then pupils holds the result; Assignment and reassignment explains assignment in full.

Fill the whole table before you run anything, and only then check it. A prediction you write down first shows you exactly which step you misread.

main.ts
let pupils = 0;
pupils = pupils + 4;
console.log("after stop 1:", pupils);
pupils = pupils + 3;
pupils = pupils - 2;
console.log("after stop 2:", pupils);

A trace table for the program above. Each row shows the next line and the values before that line runs.

A conceptual model, not a physical memory layout or live code execution.

1 / 7

Next: line 1

pupils
–
output
(none)

Nothing has run yet.

Write one row per step: the next line, the value of every variable, and the output so far.

Experiment

Fill a trace table, then check it

Each row starts from the row above it: an assignment reads the value pupils holds now, and the line after it sees the new value. So the order of the lines decides what each print shows.

Why it matters When the stepper and your table disagree, find the first row that differs. The line just before it is the one you misread.

Before running, draw a table with the columns next line, pupils, and output, and fill one row for each line. Then step and compare each stop with your rows. Then move line 6 so that it comes just before line 5, and predict the last line of output.

let pupils = 0;
pupils = pupils + 4;
console.log("after stop 1:", pupils);
pupils = pupils + 3;
pupils = pupils - 2;
console.log("after stop 2:", pupils);
What to look for

The first stop is on line 1, and pupils shows not initialized yet: the stepper’s words for a let whose line has not run. That is the dash in your table. Node stops twice on lines 3 and 6; a stepper can stop more than once on a line, and the second stop is not a new row. after stop 1: 4 appears at the stop on line 4. The last stop is on line 6 with pupils 5, and after stop 2: 5 appears when the run ends. After the edit, the last line printed is after stop 2: 7.

Expected output

after stop 1: 4
after stop 2: 5

02 / Highlight

What does the highlighted line mean?

The stepper on this page runs the real program under a debugger: a tool that runs a program and pauses it at chosen places, so you can look at the values it holds. Each pause is called a stop. At each stop one line is highlighted, and a panel shows the variables and their values.

The highlighted line is the next line to run. It has not run yet. So the panel shows the values from before that line, just like a row of your table: the next line, and the values before it.

The usual mistake is to read a value one step too early. While seats = seats - pupils is highlighted, seats still holds 40. Step once, and the panel shows 28.

main.ts
let seats = 40;
let pupils = 12;
seats = seats - pupils; // highlighted: seats is still 40
console.log("seats left:", seats); // highlighted: seats is 28

The highlighted line has not run yet; its effect shows at the next stop.

Precisely: stops and console.log

Node’s debugger can stop more than once on one line, as it does on the console.log lines here. A line’s output appears after the stepper has moved past its last stop on that line. So the output of the program’s last line appears when the run ends: no stop comes after it.

Experiment

Read a value one step too early

A stop comes before its line. The value that the highlighted line writes appears at the next stop.

Why it matters Read a line’s result at the stop after it, never at the stop on it.

Step until line 3 is highlighted. Before you look at the panel, write down the value of seats you expect to see there. Then add the line seats = seats - 5; just after line 3, and predict what seats shows at the stop on that new line.

let seats = 40;
let pupils = 12;
seats = seats - pupils;
console.log("seats left:", seats);
What to look for

While line 3 is highlighted, seats shows 40: the subtraction has not run yet. 28 appears at the stop on line 4. Node stops twice on line 4, and seats left: 28 appears when the run ends. After the edit, the stop on the new line 4 shows seats 28, not 23; 23 appears at the stop on line 5, and the run prints seats left: 23.

Expected output

seats left: 28

03 / Loop

How do you trace a loop?

A loop runs the same lines again and again. An if (test) { … } runs its block once when the test is true (Conditions and truthiness explains if in full). A while loop is an if that repeats: while (busStop <= 3) { … } checks busStop <= 3, runs the block if it is true, and then checks again. When the check is false, the run goes on after the closing brace. One run of the block is called an iteration.

In a trace table, a loop does not shrink to one row. Each iteration gets its own rows, one per line, and every check of the condition gets a row too. The last check is the one that is false, and it is the row where the loop ends. Here the loop runs for stops 1, 2, and 3, so the condition is checked four times; the fourth time, the stop number is 4.

main.ts
let busStop = 1;
let pupils = 0;
while (busStop <= 3) {
  pupils = pupils + busStop;
  console.log("stop", busStop, "pupils", pupils);
  busStop = busStop + 1;
}
console.log("at school:", pupils); // at school: 6

Give every iteration its own rows, and write down every check of the condition, including the last one, which is false.

Coming from Python: The parentheses around the condition and the braces around the block are required in TypeScript; indentation alone does not make a block.

Experiment

Trace a loop iteration by iteration

The condition is checked before every iteration, and once more after the last one. That last check is false, and it is the step that ends the loop.

Why it matters When a loop runs one time too few or too many, look at the row of its last check: the values there decide it.

Before running, fill a table with one row for each step: the next line, busStop, pupils, and the output. Count how many times line 3, the check, appears in your table. Then change <= to < on line 3 and predict the output.

let busStop = 1;
let pupils = 0;
while (busStop <= 3) {
  pupils = pupils + busStop;
  console.log("stop", busStop, "pupils", pupils);
  busStop = busStop + 1;
}
console.log("at school:", pupils);
What to look for

Line 3 is highlighted four times, with busStop 1, 2, 3, and 4. The fourth check is false, and the next stop is line 8. Node stops twice on line 5, as on other console.log lines. Each stop N pupils line appears at the stop on line 6, and at school: 6 appears when the run ends. After the edit, line 3 is highlighted three times, the third time with busStop 3, and the run prints two stop lines and at school: 3.

Expected output

stop 1 pupils 1
stop 2 pupils 3
stop 3 pupils 6
at school: 6

04 / Call

How do you trace a function call?

A function call adds one more column to the table: the stack. A frame is the record of one function call that has not finished: its parameters and local variables. The call stack is the list of these frames, newest on top.

When the run reaches const spare = seatsLeft(40, onBus), that line starts but cannot finish: it waits for the result. Write seatsLeft on top of the stack column, add its parameters seats and pupils, with 40 and 12, as new columns, and trace its lines. When return left runs, cross seatsLeft off the stack and go back to the waiting line, which now gets 28. Function calls follows a call in more detail.

main.ts
function seatsLeft(seats: number, pupils: number): number {
  const left = seats - pupils;
  return left;
}

const onBus = 12;
const spare = seatsLeft(40, onBus);
console.log("spare seats:", spare); // spare seats: 28

At a call, put the function on top of the stack column and trace its lines; at its return, go back to the line that is waiting.

Coming from Java: There is no main at the bottom of the stack: the code outside every function is the bottom frame, which the stepper calls <top level>.

Experiment

Follow a call with a stack column

A call adds a frame on top of the caller’s, and the caller’s line waits. When the call returns, its frame is removed and the waiting line gets the result.

Why it matters At each stop inside a function, the stack column tells you which line will get the result.

Add a stack column to your table: the functions that have started and not finished, newest first. Fill it before running. Then change seatsLeft(40, onBus) to seatsLeft(seatsLeft(40, 2), onBus) and predict the stack column again.

function seatsLeft(seats: number, pupils: number): number {
  const left = seats - pupils;
  return left;
}

const onBus = 12;
const spare = seatsLeft(40, onBus);
console.log("spare seats:", spare);
What to look for

The first stop is on line 6. The panel also lists seatsLeft as function seatsLeft(seats, pupils): the function itself, a value like any other. At line 7 the frame list shows only <top level>. The next stop is on line 2, and seatsLeft sits above <top level>, with seats 40 and pupils 12. Select <top level>: it is still on line 7, waiting, and spare is not initialized yet. After line 3, seatsLeft leaves the list, and the stop on line 8 shows spare 28. After the edit, seatsLeft runs twice, one call after the other: first with seats 40 and pupils 2, giving 38, then with seats 38 and pupils 12. The two frames are never in the list at the same time, and the run prints spare seats: 26. The inner call seatsLeft(40, 2) runs first because its result is an argument of the outer call.

Expected output

spare seats: 28

05 / Try it

Can you explain a fresh example?

Trace this program with a table before you reveal the answer: one row for each check of the loop condition, with the stop number and minutes.

main.ts
function travel(minutes: number, busStop: number): number {
  return minutes + busStop * 2;
}

let minutes = 0;
let busStop = 1;
while (busStop <= 3) {
  minutes = travel(minutes, busStop);
  busStop = busStop + 1;
}
console.log("minutes:", minutes);

How many times is the function called, and what does the program print?

Reveal the reasoning

Three calls, and the program prints minutes: 12. At the four checks, the stop number and minutes are 1 and 0, 2 and 2, 3 and 6, then 4 and 12; the fourth check is false. Each call starts from the minutes of the row above: travel(0, 1) gives 2, travel(2, 2) gives 6, and travel(6, 3) gives 12. At a stop on the line that calls the function, minutes still shows the value from before that call.

The next lesson, What is a value?, looks at the values in these columns and what their types decide.

Practice tickets

  1. TRACE-101 · The lunch queue screen shows one group too many → Free
  2. TRACE-102 · Reading cards show 10 points too many from the fifth book on → Sign in · Pro
  3. TRACE-103 · The sports day timetable leaves out an event that would fit → Sign in · Pro

Keep exploring

What is a value? →

Primary references