unrust

Code tracing questions: 10 practice problems with answers

Published

By the Unrust team · 12 min read · Editorial method

Ten code tracing questions with worked answers: operator precedence, if/elif chains, loops, break, scope, references, closures, recursion and binary search.

What code tracing questions look like

A code tracing question gives you a short program and asks what it does without running it: what it prints, the final value of a variable, how many times a loop body runs, which branch executes, or what a function returns. You answer by simulating execution one statement at a time and recording every value that changes.

The wording varies. Exams say “trace through the following code and select what it displays” or “what is the value of total after the loop?”. Interviews say “walk me through what this returns”. Some questions ask you to fill in a trace table, and multiple-choice versions usually offer wrong options that match specific misreadings. The ten questions below cover the common patterns in Python, JavaScript and exam pseudocode, and each one has a hidden worked answer.

Question type       Typical prompt                            What to track
-----------------   ---------------------------------------   ----------------------------------------
Output prediction   "What does this program print?"           Every print or console.log, in order
Value trace         "What is x after line 6 runs?"            One variable at one exact moment
Condition trace     "Which branch runs when age is 70?"       Each test, top to bottom, until one is True
Loop count          "How many times does the body run?"       Every check of the condition, incl. the last
Return value        "What does f(4072) return?"               Each call's argument and its return value
Edge input          "Which input gives a wrong result?"       Zero, empty, negative, equal to a boundary
Spot the line       "Which line sets the adult price?"        The first line whose effect breaks the spec

How to answer a code tracing question

Read the question before the code, so you know whether it wants output, a value, a count or a line. Then write each variable's starting value, execute one statement at a time, write every new value as soon as it is assigned, and mark each condition True or False before acting on it. Format output exactly as the program would.

Many lost marks come from a skipped step rather than hard code: an elif evaluated after an earlier branch already ran, a loop's final failed check left out, or two names for one list traced as separate copies. Under time pressure, keep notes on the values that decide the outcome: the loop variable, the value each branch tests, and any object two names share.

1. Read the question first: output, a value, a count or a line?
2. Write each variable's starting value.
3. Execute one statement at a time; write the new value immediately.
4. For every if or while test, write True or False before moving on.
5. For loops, record the final failed check as well as the iterations.
6. For calls, write the arguments, then the return value when it comes back.
7. Format output exactly: spaces, commas, brackets, quotes, line breaks.

Question 1: operator precedence (Python)

This question tests operator precedence: the rules that decide which operator is applied first. In Python, ** binds tighter than a unary minus on its left, and *, /, // and % share one level, grouped left to right, above + and -. Predict all four printed lines before opening the answer.

a = 2
b = 3
c = 4
print(a + b * c)
print((a + b) * c)
print(a + b * c // 5 % 3)
print(-a ** 2)
Reveal the answer and explanation

The program prints 14, 20, 4 and -4 on four lines. In a + b * c the multiplication happens first: 3 * 4 is 12, then 2 + 12 is 14. Parentheses override that order, so (a + b) * c is 5 * 4, which is 20.

The third line is the one to slow down on. Because *, // and % have equal precedence and group left to right, b * c // 5 % 3 means ((3 * 4) // 5) % 3: 12 // 5 is 2, and 2 % 3 is 2, so the line prints 2 + 2 = 4. The last line prints -4, not 4, because ** binds tighter than the minus sign on its left: -a ** 2 means -(a ** 2). Exam pseudocode follows the same idea; the AP Computer Science Principles reference sheet gives MOD the same precedence as * and /.

Question 2: which branch runs? (Python if/elif/else)

Condition-trace questions ask which branch of an if/elif/else chain runs. Python tests each condition from the top and runs only the first one that is True; every later branch is skipped, even if its condition would also be True. Predict the five prices, name the line that charges a 25-year-old, and decide whether the else branch can ever run.

def ticket_price(age):
    if age < 13:
        return 8
    elif age >= 65:
        return 10
    elif age >= 18:
        return 15
    elif age < 18:
        return 12
    else:
        return 0

print(ticket_price(12), ticket_price(70), ticket_price(4), ticket_price(16), ticket_price(13))
Reveal the answer and explanation

The program prints 8 10 8 12 12. Ages 12 and 4 stop at the first test. Age 70 fails age < 13 and passes age >= 65. Ages 16 and 13 fail the first three tests and reach age < 18, which returns 12. A 25-year-old fails the first two tests and is charged by return 15 under elif age >= 18.

For any integer, the else branch is unreachable. Once age < 13, age >= 65 and age >= 18 are all False, age is between 13 and 17, so age < 18 must be True. The one numeric input that reaches it is a float NaN: every ordering comparison with float('nan') is False, so ticket_price(float('nan')) falls through all four tests and returns 0. “Can the else ever run?” questions reward checking unusual inputs, not only the ordinary range.

Question 3: select what it displays (exam pseudocode)

Exam boards often write tracing questions in pseudocode. The AP Computer Science Principles reference sheet uses ← for assignment, DISPLAY for output and REPEAT UNTIL for a loop that repeats until its condition is true, and its lists start at index 1. Trace the loop and choose what it displays: (A) 15, (B) 20, (C) 24 or (D) 26.

nums ← [6, 2, 9, 4, 5]
total ← 0
i ← 1
REPEAT UNTIL(i > LENGTH(nums))
{
  IF(nums[i] > 4)
  {
    total ← total + nums[i]
  }
  i ← i + 1
}
DISPLAY(total)
Reveal the answer and explanation

The answer is (B) 20. The loop visits i = 1 through 5 and reads 6, 2, 9, 4 and 5. Only 6, 9 and 5 are greater than 4, so total becomes 6 + 9 + 5 = 20. The loop ends once i becomes 6, the first value for which i > LENGTH(nums) is true.

Each wrong option here matches one misreading. 26 is the sum of every element, so the IF was ignored. 24 includes the 4, so > was read as ≥. 15 drops the last element, which is what happens if you stop one step early out of a zero-based habit, where the last index is LENGTH - 1. When your answer matches a distractor, name the misreading that produces it before moving on.

Question 4: find the input that breaks it (Python while loop)

Edge-input questions give you a function that works on ordinary values and ask where it fails. Trace the loop for each call, counting how many times the condition n > 0 is checked. Then decide which of the three results is wrong for the stated job: counting the decimal digits in a nonnegative integer.

def count_digits(n):
    digits = 0
    while n > 0:
        n = n // 10
        digits += 1
    return digits

print(count_digits(4096))
print(count_digits(7))
print(count_digits(0))
Reveal the answer and explanation

It prints 4, 1 and 0. For 4096, n takes the values 4096, 409, 40, 4 and 0: the body runs four times and the condition is checked five times, the last check being 0 > 0, which is False. For 7 the body runs once and n becomes 0.

count_digits(0) is wrong. 0 has one digit, but 0 > 0 is False on the very first check, so the body never runs and the function returns 0. A while loop can run zero times, and tracing the first check with the smallest legal input is the fastest way to find that. One fix is to return 1 early when n == 0. Negative numbers would need their own rule too: count_digits(-5) also returns 0.

Question 5: break inside a nested loop (JavaScript)

Nested-loop questions test two rules: the inner loop restarts from its initial value on every outer iteration, and break leaves only the innermost loop that contains it. Predict the value this prints. Then predict it again with break replaced by continue, and explain why the two answers differ.

let count = 0;
for (let i = 0; i < 5; i++) {
  for (let j = 0; j < i; j++) {
    if (j === 2) break;
    count++;
  }
}
console.log(count);
Reveal the answer and explanation

It prints 7. The inner loop runs while j < i, so it adds nothing when i is 0, adds 1 when i is 1 and adds 2 when i is 2. When i is 3 or 4, j reaches 2 and break ends that inner loop, so each of those adds 2. The total is 0 + 1 + 2 + 2 + 2 = 7, and the outer loop carries on after every break.

With continue instead, it prints 8. continue skips only the j === 2 iteration, so when i is 4 the inner loop still reaches j = 3 and adds one more. With i < 4 as the outer bound, both versions print 5, because j = 2 is already the last inner value. A test that cannot tell break from continue proves nothing about which one the code needs.

Question 6: mutation or rebinding inside a function (Python)

Function questions test the difference between mutating an object and rebinding a name. In Python, a parameter is a new local name bound to the caller's object. Calling a method such as append changes that shared object, while assigning to the parameter only points the local name somewhere else. Predict all three printed values.

def add_item(items, x):
    items.append(x)
    x = x * 10
    items = [x]
    return items

data = [1, 2]
n = 3
result = add_item(data, n)
print(data, n, result)
Reveal the answer and explanation

It prints [1, 2, 3] 3 [30]. items.append(x) mutates the list that data also names, so the caller sees 3 appended. x = x * 10 rebinds the local x to 30; the caller's n is still 3, and an integer cannot be changed in place anyway.

items = [x] creates a new list and rebinds only the local name. From that line on, items and data refer to different lists, so the function returns [30] while data keeps [1, 2, 3]. If your trace showed data as [30], you treated assignment to a parameter as if it reached back into the caller.

Question 7: shared references and a shallow copy (JavaScript)

Reference questions check whether you know when two names share one object. In JavaScript, assigning an object copies the reference, not the object, and spread syntax makes a shallow copy: a new outer object whose nested arrays and objects are still shared with the original. Predict both console.log lines.

const a = { count: 1, tags: ["x"] };
const b = a;
const c = { ...a };
b.count = 2;
c.count = 3;
c.tags.push("y");
console.log(a.count, b.count, c.count);
console.log(a.tags.length, c.tags.length, a.tags === c.tags);
Reveal the answer and explanation

It prints 2 2 3, then 2 2 true. b is the same object as a, so b.count = 2 changes a.count as well. c is a separate outer object, so c.count = 3 leaves a alone.

The spread copied the tags property by reference, so a.tags and c.tags are one array. Pushing 'y' through c is visible through a, and a.tags === c.tags is true. To give c its own nested array, copy that level too, for example with tags: [...a.tags], or use structuredClone(a) for a deep copy of plain data.

Question 8: closures in a loop, var vs let (JavaScript)

Closure questions ask what a function remembers about a variable it captured. A var declaration creates one binding shared by the whole loop, while a let in a for loop header gets a fresh binding on every iteration. Predict both lines, remembering that the functions are called only after their loops have finished.

const withVar = [];
for (var i = 0; i < 3; i++) {
  withVar.push(() => i);
}
const withLet = [];
for (let j = 0; j < 3; j++) {
  withLet.push(() => j);
}
console.log(withVar.map((f) => f()).join(","));
console.log(withLet.map((f) => f()).join(","));
Reveal the answer and explanation

It prints 3,3,3 then 0,1,2. All three var closures read the same i, and by the time they are called the loop has finished with i at 3, the value that made i < 3 False. Each let closure captured its own binding of j, holding 0, 1 and 2.

The same question often appears with setTimeout inside the loop. The timer is a detail: the answer still depends on how many bindings the loop creates and what value the shared one holds when the callback finally runs.

Question 9: trace a recursive call stack (Python)

Recursion questions are traced as a stack of calls. Write each call with its argument, keep going down until a call reaches the base case and returns without recursing, then come back up and fill in each return value. Predict what this prints and how many times total_digits is called in total.

def total_digits(n):
    if n < 10:
        return n
    return n % 10 + total_digits(n // 10)

print(total_digits(4072))
Reveal the answer and explanation

It prints 13 after four calls. Going down: total_digits(4072) needs 2 + total_digits(407), which needs 7 + total_digits(40), which needs 0 + total_digits(4). The call with 4 reaches the base case and returns 4.

Coming back up, each waiting call finishes its addition: 0 + 4 is 4, 7 + 4 is 11 and 2 + 11 is 13. Write the returns bottom-up, in the reverse order of the calls. A sum hides how the pieces combine, because addition gives the same result in any order. If the function built a string instead, returning str(n % 10) + total_digits(n // 10) with str(n) as the base case, it would return '2704', not '4072': each call puts its own last digit in front of whatever the deeper call returns.

Question 10: binary search, step by step (Python)

A binary search question asks you to track low, mid and high across iterations. Write one row per loop iteration with all three values, the element at mid and the decision it causes. Predict both printed tuples: the index and step count for 42, then the result for 10, which is not in the list.

def search(items, target):
    low, high = 0, len(items) - 1
    steps = 0
    while low <= high:
        steps += 1
        mid = (low + high) // 2
        if items[mid] == target:
            return mid, steps
        if items[mid] < target:
            low = mid + 1
        else:
            high = mid - 1
    return -1, steps

data = [3, 8, 15, 21, 30, 42, 57]
print(search(data, 42))
print(search(data, 10))
Reveal the answer and explanation

It prints (5, 2), then (-1, 3). For 42, step 1 has low 0, high 6 and mid 3. items[3] is 21, which is less than 42, so low becomes 4. Step 2 has low 4, high 6 and mid 5. items[5] is 42, so the function returns (5, 2).

For 10, step 1 checks index 3 (21), so high becomes 2. Step 2 checks index 1 (8), so low becomes 2. Step 3 checks index 2 (15), so high becomes 1. Now low is 2 and high is 1, low <= high is False, and the function returns (-1, 3). That final state, with low one past high, is the row to point to when a question asks why the search stops on a missing value.

Common mistakes on code tracing questions

Wrong answers to tracing questions cluster around a few misreadings: applying operators strictly left to right, letting more than one branch run, miscounting where a loop starts or stops, and confusing a changed object with a rebound name. The table maps each mistake to the question above that tests it and to a quick check.

When your answer and the real output disagree, do not retrace from the start. Find the first statement where your recorded state differs from the program's. That statement names the rule you misapplied, and the same rule tends to cause your next miss as well.

Mistake                                 Tested in   Quick check
-------------------------------------   ---------   -------------------------------------------
Applying operators left to right        Q1          Find **, *, //, % before + and -
Running more than one elif branch       Q2          Stop at the first True test
Off-by-one on list indices              Q3          Does the first index start at 0 or 1?
Forgetting a loop can run zero times    Q4          Trace the first check with the smallest input
Thinking break exits every loop         Q5          break leaves only the innermost loop
Treating rebinding as mutation          Q6, Q7      Did the code change the object or the name?
Using a captured variable's old value   Q8          Which binding, and its value at call time?
Adding returns in the wrong order       Q9          Write return values bottom-up
Losing track of low and high            Q10         One row per iteration with all three values
Approximate output formatting           All         Spaces, commas, brackets and quotes

Where to practise code tracing questions

Practise on questions you answer before running anything, then check with a tool that can run them. Write your prediction first, step through with a visualizer such as Python Tutor or a debugger such as pdb, and stop at the first line where the real state differs from yours. That comparison is where the learning happens.

For full trace tables on for loops, while loops, a running maximum and nested loops, see the Unrust post on trace table examples. The Unrust code-tracing guide works through a continue statement and an array alias in the same state-by-state style.

For timed practice without notes, Unrust's tracing drills use this question type: you read a short snippet in JavaScript, TypeScript or Python and predict what it prints, without running it. The free diagnostic takes about 14 minutes with AI and hints off, and samples tracing alongside five other skills: recall, comprehension, debugging, idiom and AI-slop detection. A free account then gives you a daily practice queue.

Code tracing questions: FAQ

What is a code tracing question? It gives you a fixed piece of code and asks you to work out its behavior by reading rather than running it: the output, a variable's final value, a loop count, the branch taken or a function's return value.

Why do exams and interviews use tracing questions? Answering one requires an accurate model of how the language executes code. A 2004 ITiCSE working group (Lister et al.) tested students from seven countries on predicting the outcome of short code and found many were weak at it, which suggests a fragile grasp of skills that problem-solving depends on.

Is a code tracing question the same as a trace table? No. The question is the task; a trace table is one way to answer it, with a column per variable and a row per step. Short snippets need only a few notes. For loops, nested calls and recursion, a table prevents most slips.

How do I get faster at code tracing questions? Trace a few short snippets regularly without running them first, then check. Focus on the rule behind each miss, such as precedence, zero-iteration loops or shared references, rather than repeating one snippet until you remember its answer.

Can I trace code online? Yes. Visualizers such as Python Tutor step through short programs in the browser and draw variables and objects at each step. Use them to check a prediction you have already written down, not to replace it.

References and next steps

Answer tracing questions against the clock

Take Unrust's free diagnostic with AI and hints closed: about 14 minutes in JavaScript, TypeScript or Python. Tracing is one of six skills it samples, alongside recall, comprehension, debugging, idiom and AI-slop detection.

Code tracing questions: 10 practice problems with answers | Unrust Blog