Find an off-by-one error before running the loop
Published
By the Unrust team · 6 min read · Editorial method
Trace the first check, last visit and first failed condition in a JavaScript loop. Work through exclusive bounds, larger steps and reverse loops.
Open the free companion tool →Write down the values the body should see
Imagine five display slots numbered 0 through 4. You want to print each slot number once. Before choosing an operator, write the intended sequence: 0, 1, 2, 3, 4. This gives you a concrete contract. The loop’s syntax is only an implementation of that contract.
In a JavaScript for loop, initialization happens once, the condition is checked before each visit to the body, and the update runs after each completed visit. For the example below, the start is 0, the boundary is 5 and the step is 1. The boundary is excluded: the condition becomes false as soon as i reaches 5.
for (let i = 0; i < 5; i += 1) {
console.log(i);
}
// 0, 1, 2, 3, 4Trace three moments instead of every line
Check the first visit: 0 < 5 is true, so the body sees 0. Check the last intended visit: 4 < 5 is true, so the body sees 4. Then apply the update once more: i becomes 5, and 5 < 5 is false. The body never sees 5. These three moments explain the boundary more clearly than a count alone.
Now change the comparison to <= in the explorer. The loop visits 5 because 5 <= 5 is true. Its first failed check moves to 6 <= 5. That is six visits, which violates the five-slot contract. If the body used the index to access a five-element array, it would attempt to read an extra slot. Whether that causes a visible failure depends on what the surrounding code does with the result.
A larger step changes how you reach the edge
Set the start to 1, boundary to 8 and step to 3. With <, the values are 1, 4 and 7; the first failed check is 10 < 8. The loop does not need to land exactly on the boundary to stop. This is why subtracting start from boundary does not always give the iteration count.
With these particular numbers, switching to <= makes no difference because the sequence never lands on 8. Change the boundary to 7 and compare again: < visits 1 and 4, while <= also visits 7. When testing an inclusive boundary, choose a case that actually reaches equality. Otherwise a broken operator can pass your example unnoticed.
// Start 1, step 3, boundary 7
// i < 7: 1, 4 → first failure at 7
// i <= 7: 1, 4, 7 → first failure at 10Check empty and reverse cases
A loop whose first condition is false makes no visits. Start 5 with a < 5 condition and the body never runs, regardless of a step that would have moved toward smaller numbers. The update happens after a body visit, so it cannot rescue a failed first check.
For a countdown from 4 to 0, use start 4, comparison >=, boundary 0 and step -1. The first failure is -1 >= 0. If you accidentally use a positive step, the loop moves away from the lower boundary and never finishes in this simple example. A zero step has the same problem whenever the condition starts true. The explorer detects these cases without running an unbounded loop.
Keep the model small enough to trust
The explorer models integer steps and a body that only logs i. It deliberately does not execute pasted code. If your real body changes the index, changes the boundary, breaks early or throws, its behavior can differ. Floating-point increments can also introduce rounding behavior that this integer model does not represent.
Use the tool to isolate the boundary question. Then return to the real code and list every statement that could change the sequence. Check both the intended final body value and the first rejected value. That makes the reasoning useful even when the production loop is more complicated than the example.
Try one prediction without the preview
Start at 8, stop at 2, step by -2 and use >. Write the body values and the first failed check on paper before entering them. You should get 8, 6, 4, with the first failure at 2 > 2. Then switch to >= and predict the extra visit. If you can explain why the first rejected value moved, you have practiced the boundary rather than memorized an output.
This exercise is open practice, not a diagnostic score. For a broader snapshot, Unrust’s free diagnostic measures several unassisted coding skills through its own timed drills. Keep tools and hints out of that timed sitting so the conditions remain consistent.
References and next steps
Try the reasoning without the tool
Explain one fresh example before checking its output. For a broader starting point, take the free diagnostic with tools and hints closed. A free account then gives you a daily practice queue.