Skip to content
{}

Lesson 01new · v2 format

Loops & Bounds: for and while Without Guessing

One rule decides every loop condition you will ever write: find the largest index the body touches.

30 min

01Why it matters & the intuition

You can understand bubble sort perfectly and still lose 20 minutes to IndexError: list index out of range. Almost every bug in early DSA code is a loop that starts one too early, stops one too late, or never stops.

This lesson turns loop bounds from guesswork into a rule, using the five loop shapes that make up nearly every algorithm in this course.

Think of it like…

Think of a row of seats numbered 0 to n − 1. Before you walk the row, ask one question: *which seat numbers will my hands touch?* If you look at your seat and the next one (i and i + 1), your last step must be at seat n − 2, or you'll reach for seat n, which doesn't exist.

02The code blocks

Each block: the template in Python and C++, a line-by-line trace (press play and watch the highlighted line and the variables), the mistakes that cost the most debugging time, and a recall drill. Do the drill without scrolling up— that’s what makes it stick.

Block 1

Visit every index

Use it for: Summing, printing, searching, building a new list — any time each element is handled once.

1 · The template

python
total = 0
for i in range(len(a)):
    total += a[i]
print(total)

2 · Watch it run, line by line

trace · pythona = [4, 7, 1]
1/9
▶total = 0
2for i in range(len(a)):
3 total += a[i]
4print(total)

Variables

total
0
a
0
4
1
7
2
1
line 1 ›Start the accumulator at 0 before the loop.

3 · Classic mistakes

  • for i in range(len(a) + 1):Reaches i = len(a), and a[len(a)] raises IndexError.
  • for (int i = 0; i <= n; i++)C++ version of the same bug: `<=` runs one step too far and reads garbage memory silently.

4 · Recall it without looking

recall · fill the blank
total = 0
for i in range():
    total += a[i]

Block 2

Compare neighbours (i, i + 1)

Use it for: Is it sorted? Count rises/drops, find the biggest jump, one pass of bubble sort.

1 · The template

python
rises = 0
for i in range(len(a) - 1):
    if a[i + 1] > a[i]:
        rises += 1

2 · Watch it run, line by line

trace · pythona = [3, 5, 4, 8]
1/10
▶rises = 0
2for i in range(len(a) - 1):
3 if a[i + 1] > a[i]:
4 rises += 1

Variables

rises
0
a
0
3
1
5
2
4
3
8
line 1 ›We will compare each element with the one after it.

3 · Classic mistakes

  • for i in range(len(a)): # then a[i + 1]On the last i, a[i + 1] is past the end → IndexError.
  • for i in range(1, len(a)): # then a[i + 1]Shifting the start doesn't fix the end. If you start at 1, compare a[i - 1] with a[i] instead.

4 · Recall it without looking

recall · fill the blank
for i in range():
    if a[i + 1] > a[i]:
        rises += 1

Block 3

Every pair once (nested i < j)

Use it for: Brute force over pairs: two-sum by hand, closest pair, count inversions, selection sort's inner scan.

1 · The template

python
for i in range(len(a)):
    for j in range(i + 1, len(a)):
        print(a[i], a[j])

2 · Watch it run, line by line

trace · pythona = [3, 1, 2]
1/11
▶for i in range(len(a)):
2 for j in range(i + 1, len(a)):
3 print(a[i], a[j])

Variables

i
0
a
0
3
i
1
1
2
2
line 1 ›Outer loop: i = 0.

3 · Classic mistakes

  • for j in range(len(a)):Visits (i, i) and both (1, 3) and (3, 1) — double counts.
  • for j in range(i, len(a)):Includes j = i: every element gets paired with itself.

4 · Recall it without looking

recall · fill the blank
for i in range(len(a)):
    for j in range(, len(a)):
        check(a[i], a[j])

Block 4

Walk backwards

Use it for: Right-to-left scans: suffix sums, 'next greater' with a stack, removing items while iterating.

1 · The template

python
for i in range(len(a) - 1, -1, -1):
    print(a[i])

2 · Watch it run, line by line

trace · pythona = [10, 20, 30]
1/7
▶for i in range(len(a) - 1, -1, -1):
2 print(a[i])

Variables

i
2
a
0
10
1
20
2
30
i
line 1 ›range(start=2, stop=−1, step=−1) → i = 2.

3 · Classic mistakes

  • range(len(a) - 1, 0, -1)Stop is exclusive, so index 0 is skipped.
  • for (size_t i = n - 1; i >= 0; i--)C++: an unsigned index is always ≥ 0, so this loops forever. Use int.

4 · Recall it without looking

recall · fill the blank
for i in range(len(a) - 1, , -1):
    print(a[i])

Block 5

while with a bounds guard

Use it for: When the step isn't fixed: skip while a condition holds, two pointers, binary search, reading until a sentinel.

1 · The template

python
i = 0
while i < len(a) and a[i] == 0:
    i += 1
print(i)

2 · Watch it run, line by line

trace · pythona = [0, 0, 5, 0]
1/7
▶i = 0
2while i < len(a) and a[i] == 0:
3 i += 1
4print(i)

Variables

i
0
a
0
0
i
1
0
2
5
3
0
line 1 ›A while loop needs its counter set up by hand.

3 · Classic mistakes

  • while a[i] == 0 and i < len(a):Checks a[i] before the bound — crashes when every element is 0. Guard first; `and` short-circuits.
  • (no i += 1 in the body)The condition never changes → infinite loop. Every while body must move toward the exit.

4 · Recall it without looking

recall · fill the blank
i = 0
while  and a[i] == 0:
    i += 1

03Key ideas

The one rule: largest index touched ≤ n − 1

Look at every a[...] in the loop body and find the biggest index expression. If it's a[i + 1], then i + 1 ≤ n − 1, so i ≤ n − 2, so the loop is range(n − 1) (C++: i < n - 1).

range(start, stop, step): stop is never included

range(5) → 0…4. range(2, 5) → 2, 3, 4. range(4, -1, -1) → 4…0. C++ writes the same thing as for (int i = start; i < stop; i += step).

for vs while

Use for when you know how many steps you take (each index once). Use while when the next step depends on data: two pointers moving at different speeds, binary search halving, skipping until something changes.

Every while needs three things

Setup before the loop (i = 0), a condition that eventually becomes false, and a move inside the body (i += 1). Missing the move is the #1 cause of infinite loops.

Guard first, then read

while i < n and a[i] == 0 is safe because and stops at the first false. Reversing the order reads a[n] and crashes.

04Cheat sheet

Every block from this lesson on one screen. Keep it open while you do the lab — then try the lab again without it.

python
# The five loop shapes — largest index touched decides the bound.
n = len(a)

for i in range(n):                 # every index: 0 .. n-1
    ...a[i]...

for i in range(n - 1):             # neighbours: a[i], a[i + 1]
    ...a[i], a[i + 1]...

for i in range(n):                 # every pair once, i < j
    for j in range(i + 1, n):
        ...a[i], a[j]...

for i in range(n - 1, -1, -1):     # backwards: n-1 .. 0
    ...a[i]...

i = 0                              # while: setup, guard-first condition, move
while i < n and a[i] == 0:
    i += 1

05Complexity

Loop shapeBody runs
for i in range(n)n times → O(n)—
for i in range(n − 1) (pairs i, i+1)n − 1 times → O(n)—
for i … for j in range(i + 1, n)n(n − 1)/2 times → O(n²)—
for i … for j in range(n)n² times → O(n²)—
while i < n: i *= 2 (or halving)≈ log₂ n times → O(log n)—

06Check your understanding

Question 1

Q1.The loop body reads a[i - 1] and a[i]. Which loop is correct?

Question 2

Q2.How many times does print run?

for i in range(4):
    for j in range(i + 1, 4):
        print(i, j)
Question 3

Q3.Which line makes while l < r: loop forever?

07Code lab

Write Python(or switch to JavaScript) and run it against real test cases. Python runs inside your browser — nothing is uploaded — and a C++ reference solution is under “Solution”.

Count pairs that add to a target

Write a function that returns how many pairs of different positions (i < j) have a[i] + a[j] == target. Use the every-pair loop — the point is to get both bounds right on the first try.

function: count_pairsPython starts when you get here