Skip to content
VLSI Mentor

Python Part I · Module 10 · Loops

Nested Loops: Every Test at Every Seed

A regression is rarely a list of tests. It is a list of tests run at a list of seeds, which is a different and much larger thing: forty tests at a hundred seeds is four thousand simulations, and the code that expresses it is one line longer than the code for forty. That asymmetry is what makes nested loops worth a chapter of their own. The syntax is nothing new — a loop is a statement, and a loop body holds statements — but two things about the result surprise people reliably: the order the passes actually happen in, and how many of them there are.

Foundation16 min readPythonPart IFundamentalsLoopsNesting

Part I · Module 10 · Chapter 10.11 · Nested Loops: Every Test at Every Seed

1. Two Lists, One Job List

You have tests and you have seeds, and you want every combination:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
tests = ["smoke", "reset"]
seeds = [4210, 4211, 4212]

Six runs. Writing them out is already tedious at this size and impossible at the real one.

2. A Loop Inside a Loop

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
tests = ["smoke", "reset"]
seeds = [4210, 4211, 4212]

runs = 0

for test in tests:
    for seed in seeds:
        runs += 1
        print(runs, test, seed)

print("inner body ran", runs, "times =", len(tests), "x", len(seeds))
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
1 smoke 4210
2 smoke 4211
3 smoke 4212
4 reset 4210
5 reset 4211
6 reset 4212
inner body ran 6 times = 2 x 3

Nothing new was introduced. The inner for is a statement, and the outer loop's body holds statements — Chapter 9.2's block rule, applied to a loop instead of an if. The indentation does all the work: the inner loop is at column 4 so it belongs to the outer body, and runs += 1 is at column 8 so it belongs to the inner body.

3. The Order: The Inner Loop Varies Fastest

Read the output again. smoke appears three times in a row, then reset three times. The seed changes on every line; the test changes only when the seeds run out.

That is the rule: the inner loop completes entirely for each single pass of the outer one.

Six passes in order. Passes one to three are the smoke test at seeds 4210, 4211 and 4212. The inner loop then restarts. Passes four to six are the reset test at the same three seeds, 4210, 4211 and 4212.Two tests across three seeds01FIRST OUTER PASS BEGINSsmoke with seed 421002INNER ADVANCESsmoke with seed 421103INNER EXHAUSTEDsmoke with seed 421204OUTER ADVANCES, INNER RESTARTSreset with seed 421005INNER ADVANCESreset with seed 421106BOTH EXHAUSTEDreset with seed 4212
Figure 1 — the six passes of two tests across three seeds, in the order they actually happen. The seed advances on every pass; the test advances only when the inner loop has been exhausted and restarted. Reading a nest as though both names advanced together is the usual mistake, and this is the order that disproves it.

The restart is worth seeing explicitly, because "the inner loop runs again" is easy to say and easy to picture wrongly:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
for test in ["smoke", "reset"]:
    print("outer pass for", test)
    for seed in [1, 2]:
        print("   inner", seed)
    print("   inner loop finished")
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
outer pass for smoke
   inner 1
   inner 2
   inner loop finished
outer pass for reset
   inner 1
   inner 2
   inner loop finished

The inner loop ran to completion twice, from the beginning both times. It does not resume where it left off, and the inner collection is not consumed — Chapter 10.1 Section 7 established that looping does not use a collection up, and this is where that matters.

4. The Count Is a Multiplication

The inner body runs once per combination: the length of the outer collection times the length of the inner one.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
tests = ["smoke", "reset"]
seeds = [4210, 4211, 4212]

runs = 0

for test in tests:
    for seed in seeds:
        runs += 1

print(runs, "=", len(tests), "x", len(seeds))
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
6 = 2 x 3

Three levels multiply three times:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
runs = 0

for tool in ["vcs", "xrun"]:
    for test in ["smoke", "reset"]:
        for seed in [1, 2, 3]:
            runs += 1

print("combinations:", runs)
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
combinations: 12

Two tools, two tests, three seeds — twelve runs, from a program whose three collections total seven items.

5. Building the Job List

The accumulator pattern from Chapter 10.10 works unchanged; only its position needs care:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
tests = ["smoke", "reset"]
seeds = [4210, 4211]

jobs = []

for test in tests:
    for seed in seeds:
        jobs.append((test, seed))

print(jobs)
print(len(jobs), "jobs")
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
[('smoke', 4210), ('smoke', 4211), ('reset', 4210), ('reset', 4211)]
4 jobs

jobs = [] is outside both loops, because the result belongs to the whole expansion. Putting it between the two for lines would reset it on every outer pass, which is Chapter 10.10 Section 3's bug with an extra level to hide in — and there it would leave you with only the last test's jobs.

6. break Leaves One Loop

Chapter 10.7 established this; here is where it bites:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
for test in ["smoke", "reset"]:
    for seed in [1, 2, 3]:
        if seed == 2:
            break
        print(test, seed)

print("done")
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
smoke 1
reset 1
done

The inner loop broke on both outer passes, and the outer loop carried on regardless. There is no statement that leaves two loops at once.

When you genuinely need to stop everything — the first fatal in any test at any seed means the rest of the sweep is pointless — the available answer is a flag checked by the outer loop:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
stop = False

for test in ["smoke", "reset"]:
    for seed in [1, 2, 3]:
        if test == "reset" and seed == 1:
            stop = True
            break
        print(test, seed)
    if stop:
        break

print("done")
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
smoke 1
smoke 2
smoke 3
done

Two breaks and a flag: the inner one leaves the inner loop, the flag carries the decision outward, and the outer one acts on it.

7. What Nesting Costs

The asymmetry is the thing to carry away. Adding one level costs one line to write and a multiplication to run:

OuterInnerInner body runs
2 tests3 seeds6
10 tests10 seeds100
40 tests100 seeds4,000
40 tests100 seeds × 2 tools8,000

At four thousand simulations of ninety seconds each, that nest is four days of machine time on one host. Nothing in the code looks four days long.

This is not an argument against nesting — expanding a sweep is exactly what the construct is for, and a regression grid is a real requirement. It is an argument for knowing the number before you run it. Printing len(tests) * len(seeds) before submitting anything costs one line and has saved a great many wasted nights.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
tests = ["smoke", "reset", "burst", "cdc"]
seeds = [4210, 4211, 4212]

print("about to submit", len(tests) * len(seeds), "runs")
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
about to submit 12 runs

8. When a Nest Is the Wrong Shape

A nest produces every combination. If the pairs you want are not every combination — because some are excluded, or because they already exist as data — then one loop is correct and nesting is actively wrong:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
jobs = [("smoke", 4210), ("smoke", 4211), ("reset", 4210)]

for test, seed in jobs:
    print(test, seed)

print("one loop, three passes")
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
smoke 4210
smoke 4211
reset 4210

Three jobs, not four. reset at 4211 is deliberately absent — perhaps it is a known failure — and no nest can express that, because a nest has no way to omit a combination without an if inside it.

The rule: nest to generate combinations; loop once over pairs that already exist. Chapter 6.5's unpacking in the for line is what makes the second read as well as the first.

Chapter 10.3 Section 9 is the related case worth re-reading: a list of dictionaries is walked with one loop, because reaching inside each item is a lookup rather than an iteration. Needing a second loop means the inner thing is itself a collection you must visit every element of.

9. Predict, Run, Observe

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
# A
count = 0
for a in [1, 2]:
    for b in [10, 20, 30]:
        count += 1
print("A:", count)

# B
for a in ["x", "y"]:
    for b in [1, 2]:
        print("B:", a, b)

# C
total = 0
for a in [1, 2, 3]:
    for b in [10, 20]:
        total += a * b
print("C:", total)

# D
pairs = []
for a in [1, 2]:
    for b in ["p", "q"]:
        pairs.append((a, b))
print("D:", pairs)

The verified output:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
A: 6
B: x 1
B: x 2
B: y 1
B: y 2
C: 180
D: [(1, 'p'), (1, 'q'), (2, 'p'), (2, 'q')]

A — 2 × 3.

B — the inner name varies fastest; x is paired with both values before y appears at all.

C — six combinations, so 10 + 20 + 20 + 40 + 30 + 60, which is 180. It also equals (1 + 2 + 3) × (10 + 20), because every a meets every b exactly once — a useful check, and one that only holds because a nest really does produce the full grid.

D — four pairs in the order Section 3 predicts.

10. Exercises

Exercise 1 — count before running. For 7 tests, 25 seeds and 2 simulators, give the number of runs without writing a loop. Then write the three-level nest with a counter and confirm it.

Exercise 2 — predict the order. Write down the first four lines of output for a nest over ["a", "b"] and [1, 2, 3], then verify. Say in one sentence which name changes on every line.

Exercise 3 — the restart. Add a line that prints inner finished after the inner loop but inside the outer one. Predict how many times it appears for a 3 × 4 nest, then verify.

Exercise 4 — build the grid. Build the full job list for ["smoke", "reset", "burst"] across seeds range(4210, 4213) as a list of tuples. Print the first job, the last job and the total.

Exercise 5 — the misplaced accumulator. Take Exercise 4 and move jobs = [] to between the two for lines. Predict the output, verify, and explain in two sentences which jobs survived and why.

Exercise 6 — leave both loops. Write a nest over tests and seeds that stops entirely at the first combination where the seed is 4212, printing everything before it. Verify, then say what you would write instead once you have functions.

Exercise 7 — nest or not. For each, say whether it wants a nest or one loop: (a) every test at every seed (b) a list of already-chosen test-and-seed pairs (c) every character of every signal name in a list (d) a list of dictionaries, printing one field of each.

Exercise 8 — exclude a combination. Expand two tests across three seeds, but skip reset at seed 4211. Write it as a nest with a continue, then as one loop over an explicit list of pairs, and say which you would prefer if the exclusion list grew to twenty entries.

11. Summary

  • A loop inside a loop is not new syntax — a loop is a statement, and a body holds statements. The indentation says which body each line belongs to.
  • The inner loop varies fastest, completing entirely for each single pass of the outer one.
  • The inner loop restarts from the beginning on every outer pass; the inner collection is not consumed.
  • The inner body runs len(outer) × len(inner) times — a multiplication, and it compounds with each extra level.
  • An accumulator for the whole expansion goes outside both loops. Between them, it resets on every outer pass.
  • break leaves one loop. Leaving both needs a flag and a second break; Module 11's functions give a cleaner answer.
  • One extra line of code multiplies the runtime. 40 tests at 100 seeds is 4,000 runs, and nothing about the code looks that large.
  • Print the product before submitting anything.
  • Nest to generate every combination; loop once over pairs that already exist. A list of tuples wants one loop and unpacking in the for line.

The last chapter of the module collects the mistakes that loops make available, including the two that produce no error at all.

Where this fits

Part of the Python curriculum.