التتبّع اليدوي بجدول المتغيرات
في هذا الدرس 11 موضوعات
هذا الدرس متاح حاليًا باللغة الإنجليزية فقط، ولم تُنشر نسخته العربية بعد.
Lesson Overview
You have a plan in pseudocode, or you have Java code that gives the wrong answer and you cannot see why. In both cases the most reliable move is the same: stop guessing and run the program on paper.
Hand-tracing means executing a program yourself, one line at a time, and recording the value of every variable after each step in a variable table. It sounds slow, yet it is often the fastest way to find a bug, because it forces you to see what the code does instead of what you believe it does. This is the trip analogy again: you check the route on the map, town by town, before you spend fuel on it.
Why this matters: your eyes skip over code, and your brain fills in what you meant. A table does not. It also teaches you to think like the computer, the skill the debugger later automates.
In the previous lesson, "Pseudocode and flowcharts", you wrote plans. Here you learn to check a plan or a program by executing it.
Learning Objectives
By the end of this lesson you will be able to:
- Build a variable table with one column per variable and one row per step.
- Execute a loop on paper, recording values after each change.
- Compare the table with your expected values and find the first row that differs.
- Trace code that uses an array, a condition, and a method call.
- Use a trace to find and fix a bug.
Written Explanation
The variable table
A variable table has:
- one column for each variable that matters (and for any condition worth recording),
- one row for each moment you want to record, usually after each pass of a loop or each change of a variable.
Here is the procedure:
- List the variables of the code you are tracing and make the columns.
- Pick a small input. Small means three or four values, so the table stays short.
- Write the starting row with the values the variables have before the loop.
- Execute one line at a time, exactly as written, and write down every value that changes. Do not skip ahead and do not "fix" things as you go.
- When the loop condition becomes false, stop. Write the result.
- Compare the table to what you expected.
The rule that makes tracing work is step 4: act like the computer, which does what the code says, not what you intended.
A first table
Take sum = 0 then FOR i FROM 1 TO 3: sum = sum + i * i.
| step | i | sum |
|---|---|---|
| start | - | 0 |
| 1 | 1 | 1 |
| 2 | 2 | 5 |
| 3 | 3 | 14 |
Each row is the state after one pass. The final 14 matches the hand calculation 1 + 4 + 9.
Finding the bug: the first row that differs
Write an expected value next to what you get. A bug sits at the first place where the actual value differs from the expected one. Later rows may also be wrong, but they are only consequences. So: write down what you expect in the key rows, trace what the code does, find the first row where they differ, and look at the single line that produced it.
Tracing conditions and arrays
When a loop contains an if, add a column for the condition result, so you can see which branch ran. With arrays, add a column for the index i and one for the element a[i]; if the array is changed by the code, record the array contents too. Write arrays in braces, for example {3, 8, 2}.
Tracing method calls
When the code calls a method, treat the call as one step if you trust the method, or step inside it with a table for its variables: the parameters take the argument values and the locals start fresh, as with scope and the call stack. Write the returned value into the caller's table.
Tips for good traces
- Small inputs first. If the trace is long, the input is too big.
- Record, do not remember. Anything not in the table does not exist.
- Use the unusual example. A tie, a zero, or a single element often reveals what the typical example hides.
- A debugger does the same thing automatically, as you will see later in the course.
Examples
Example 1: a loop with a condition (simple)
Trace this code and say the final value:
int sum = 0;
for (int i = 1; i <= 5; i++) {
if (i % 2 == 0) sum += i; else sum -= 1;
}| step | i | i % 2 == 0 | sum |
|---|---|---|---|
| start | - | - | 0 |
| 1 | 1 | false | -1 |
| 2 | 2 | true | 1 |
| 3 | 3 | false | 0 |
| 4 | 4 | true | 4 |
| 5 | 5 | false | 3 |
When i becomes 6 the test i <= 5 fails and the loop ends. The final value of sum is 3. Printing sum inside the loop gave exactly -1, 1, 0, 4, 3.
Example 2: tracing finds a bug (practical)
Problem: "count how many times the largest value appears in an array". A beginner writes:
static int countLargest(int[] a) {
int max = a[0];
int count = 0;
for (int i = 1; i < a.length; i++) {
if (a[i] > max) { max = a[i]; count = 1; }
else if (a[i] == max) { count++; }
}
return count;
}It looks fine, and for {3, 8, 2, 8} it prints 2, which is right. Tracing an unusual input tells a different story. Take {8, 3, 8} and expect 2. Write the expected values first: after the whole array, max is 8 and count is 2.
| step | i | a[i] | max | count | expected count |
|---|---|---|---|---|---|
| start | - | - | 8 | 0 | 1 |
| 1 | 1 | 3 | 8 | 0 | 1 |
| 2 | 2 | 8 | 8 | 1 | 2 |
The method returns 1 but the answer should be 2. The first row that differs from expected is the start row: before the loop, max is 8 and the value 8 has been seen once, so count should already be 1. The bug is the line int count = 0;, which forgets the first element. The fix is int count = 1;. Running the buggy code on {3, 8, 2, 8}, {8, 3, 8}, and {8, 3, 2} printed 2, 1, and 0. The last one is clearly wrong, since 8 appears once.
The loop is fine. Without an expected column you would have stared at it.
Example 3: a method call with a condition (realistic)
Here is the grade report from the decomposition lesson, traced on {80, 105, 70} through its steps for the sum and count of valid grades. We combine them in one table to see the whole run.
| step | i | g | valid? | sum | count |
|---|---|---|---|---|---|
| start | - | - | - | 0 | 0 |
| 1 | 0 | 80 | true | 80 | 1 |
| 2 | 1 | 105 | false | 80 | 1 |
| 3 | 2 | 70 | true | 150 | 2 |
After the loop the average is 150 divided by 2, which is 75.0; a program that prints each row gave the same values. In this table valid? is the result of calling isValid(g); because we trust that method, we recorded one value instead of stepping inside it. If isValid were suspect, we would trace it separately on 105: 105 >= 0 is true, 105 <= 100 is false, so the result is false.
Backend Insight
Tracing is how experienced developers read unfamiliar code and diagnose faulty behaviour. When a production report shows a wrong total, nobody guesses; they pick a small failing input, follow the data through each step, and find the first place the data differs from what the business expects. Tools such as logs and debuggers show the values of variables at each step, which is the same variable table produced by a machine.
The habit also helps in code review, where a tricky condition is often traced on one or two examples, including a boundary value.
Common Mistakes
Mistake 1: Tracing what you meant, not what the code says
- Incorrect: writing
count = 1in the start row because that is what you intended. - Correct: writing
count = 0because the code saysint count = 0;.
Mistake 2: Skipping the table and "just reading"
The brain fills gaps. Make the table, even a small one.
Mistake 3: Not writing expected values
Without an expected column you only see numbers, with nothing to compare them to.
Mistake 4: Forgetting the last pass
Many bugs live in the final row of a loop: whether the last element is included, and whether the test stops one pass too early or too late. Always write the row in which the test fails.
Exercises
Try each exercise yourself before opening the solutions. Run the code afterwards to check your trace.
Level A: Foundation
A1. What are the columns and rows of a variable table?
أظهر الحل A1
A1. One column for each variable that matters (and any condition worth recording), and one row for each moment you record, such as after each pass of a loop.
A2. Trace and give the final values of a and b:
int a = 3;
int b = 4;
a = a + b;
b = a - b;
a = a - b;أظهر الحل A2
A2.
| step | a | b |
|---|---|---|
| start | 3 | 4 |
| 1 | 7 | 4 |
| 2 | 7 | 3 |
| 3 | 4 | 3 |
Final: a is 4 and b is 3. The three lines swap the two values without a third variable. Running it printed 4 3.
A3. Where in a trace do you look for a bug?
أظهر الحل A3
A3. At the first row that differs from the expected value. That row points to the line that caused it.
Level B: Application
B1. Trace this loop and give the final value of c, with a table:
int x = 20;
int c = 0;
while (x > 3) {
x = x - 5;
c++;
}أظهر الحل B1
B1.
| step | x | x > 3 before the pass | c |
|---|---|---|---|
| start | 20 | - | 0 |
| 1 | 15 | true | 1 |
| 2 | 10 | true | 2 |
| 3 | 5 | true | 3 |
| 4 | 0 | true | 4 |
Now x > 3 is false (0 is not above 3), so the loop ends. Final c is 4, and x is 0 (the run printed 0 4).
B2. Trace countLargest from this lesson with the fix int count = 1; on {8, 3, 2} and {5, 5, 5}. What does each call return?
أظهر الحل B2
B2. For {8, 3, 2}: start max 8, count 1; i=1: 3 is not above 8 and not equal, no change; i=2: same. Returns 1. For {5, 5, 5}: start max 5, count 1; i=1: equal, count 2; i=2: equal, count 3. Returns 3. (Traced by hand; the buggy version's outputs shown in the lesson were run.)
B3. A student traces a method and writes the table below for sum of {2, 4} with a loop for (int i = 1; i < a.length; i++) sum += a[i];. The student expected 6. Which row is the first to differ, and what is the bug?
| step | i | sum | expected sum |
|---|---|---|---|
| start | - | 0 | 0 |
| 1 | 1 | 4 | 2 |
أظهر الحل B3
B3. The first differing row is row 1: the table shows sum 4 where 2 was expected. The loop starts at index 1, so the first element, 2, is skipped. The fix is to start at i = 0. The final answer would be 4 instead of 6.
Level C: Challenge
C1. Trace this nested loop with a table that has columns i, j, and count, and give the final count:
int count = 0;
for (int i = 1; i <= 3; i++) {
for (int j = 1; j <= i; j++) {
count++;
}
}أظهر الحل C1
C1.
| i | j | count |
|---|---|---|
| start | - | 0 |
| 1 | 1 | 1 |
| 2 | 1 | 2 |
| 2 | 2 | 3 |
| 3 | 1 | 4 |
| 3 | 2 | 5 |
| 3 | 3 | 6 |
Final count is 6 (the run printed 6). The inner loop runs i times for each i: 1 + 2 + 3.
C2. This code is meant to reverse an array in place. Trace it on {1, 2, 3, 4} (columns: i, the swapped pair, and the array) and explain the bug.
for (int i = 0; i < r.length; i++) {
int t = r[i];
r[i] = r[r.length - 1 - i];
r[r.length - 1 - i] = t;
}أظهر الحل C2
C2. Pairs for {1, 2, 3, 4}: i=0 swaps positions 0 and 3: {4, 2, 3, 1}; i=1 swaps positions 1 and 2: {4, 3, 2, 1}; i=2 swaps positions 2 and 1 again: {4, 2, 3, 1}; i=3 swaps positions 3 and 0 again: {1, 2, 3, 4}. The first row that differs from the expected reversed array is i=2, where an already-swapped pair is swapped back. The loop must stop at the middle (i < r.length / 2). Running the buggy code printed [1, 2, 3, 4], the original array.
Mini Challenge
Take this code and the input {4, 7, 7, 2}:
int best = a[0];
int index = 0;
for (int i = 1; i < a.length; i++) {
if (a[i] > best) {
best = a[i];
index = i;
}
}- Write what you expect
bestandindexto be at the end for this input, before tracing. - Trace it with columns
i,a[i],a[i] > best,best, andindex. - Say what happens with the tie, and whether that agrees with "the first largest value".
- Change the code so that it returns the position of the last largest, and trace your change on the same input.
Check your work: your table has a start row and a row for every pass, expected values were written first, and you identified the line that decides the tie.
Summary & Next Lesson
Key takeaways
- Hand-tracing means executing code one line at a time and recording variable values in a table: columns for variables, rows for steps.
- Act like the computer: follow what the code says, not what you meant.
- Write expected values. The bug is at the first row that differs.
- Trace small inputs, and include a column for any condition and the row where a loop ends.
- A debugger does this automatically later, but tracing builds the skill.
Next lesson
You can now check a plan on one input. The next lesson, "Thinking in edge cases", is about choosing which inputs to check: the unusual ones at the limits where code tends to break.
Self-check
- What does each row of a variable table represent?
- Where is the bug in a trace?
- Why write expected values?
- Why should the last pass of a loop get its own row?
- When you trace a call to a method you trust, what can you write instead of stepping inside it?
أظهر الإجابات
- The values of the variables at one moment, usually after one pass of a loop or one change.
- At the first row that differs from what you expected.
- They give you something to compare with, so you can find the first row that differs.
- Many bugs concern whether the loop stops too early or too late, and whether the last element is included.
- The value the method returns, in one step.
References
- Oracle, The Java Tutorials: The for Statement. Supports how the three parts of a
forloop are evaluated, which hand-tracing follows. - Oracle, The Java Tutorials: The while and do-while Statements. Supports that a
whileloop tests its condition before each pass, as used in B1. - Every Java snippet in this lesson was compiled and run with JDK 21, and the values quoted as "ran" are real output. The variable-table method is a standard teaching technique and no single source is cited for it.
التطبيق
طبّق هذا الدرس بمختبر الوحدة وتمارينها واختبارها.
تعليم الدرس كمكتمل اختياري ويمكنك التراجع عنه في أي وقت. يبقى تقدمك محفوظًا في هذا المتصفح.