Nested Selection and Iteration

You already know two of the great powers of programming: selection — making a choice with if — and iteration — repeating work with a loop. The exciting next step is realising you can put these building blocks inside each other. An if can live inside another if. A loop can live inside another loop. When one control structure sits inside another, we say it is nested — the word we use for a smaller box tucked inside a bigger one.

Think of a set of instructions like "for each pupil in the class, mark each question on their test". There are two "for each"s there, one inside the other: the outer one steps through the pupils, and for every single pupil the inner one steps through all the questions. That is a nested loop, and it captures something computers do constantly. This page is all about that one idea: things inside things, and the golden rule that comes with it — the inner thing runs fully on every pass of the outer thing.

Nested selection: a decision inside a decision

Sometimes one choice isn't enough — after you've made the first decision, you need to make a second one within it. Imagine grading a test out of 100. First we decide pass or fail at 50. But if the pupil passed, we then want to decide how well — a merit, or a top-grade distinction. That second decision only makes sense once we already know they passed, so it belongs inside the "passed" branch. Run this:

const score: number = 82; if (score >= 50) { console.log("You passed! 🎉"); // This inner decision only runs when the outer one was true. if (score >= 80) { console.log("...with a distinction — outstanding!"); } else if (score >= 65) { console.log("...with a merit — well done!"); } } else { console.log("Not a pass this time — keep going."); }

See how the inner if is tucked neatly inside the outer one, pushed further to the right? That indentation isn't decoration — it's how you (and every other programmer) see the nesting at a glance. The inner check about distinctions is never even looked at for a failing score, because we never enter the outer if at all. Change score to 40, then to 70, and watch which messages appear.

Nested iteration: a loop inside a loop

Now the star of the show. When you put one loop inside another, the outer loop starts a pass, and before it can move on, the inner loop runs all the way through from start to finish. Then the outer loop takes its next step, and the inner loop runs completely again. Over and over.

The classic first example is drawing a rectangle of stars. The outer loop handles the rows; for each row, the inner loop prints the stars across, and then we move to a fresh line. Run it and count:

const rows: number = 4; const cols: number = 6; for (let r = 0; r < rows; r++) { // OUTER loop: one pass per row let line = ""; for (let c = 0; c < cols; c++) { // INNER loop: runs fully for every row line = line + "★ "; } console.log(line); // print the finished row }

The outer loop ran 4 times. But the inner line = line + "★ " happened 6 times per row — so it ran 4 \times 6 = 24 times in total. That multiplication is the whole secret of nested loops, and we'll come back to it.

Watch the passes stack up

Before more code, let's see exactly what "the inner loop runs fully on each outer pass" looks like. Step through a small 3 \times 4 grid below: the outer counter r waits patiently on one row while the inner counter c sweeps all the way across, then r moves down and the sweep starts over.

By the end, every one of the 3 \times 4 = 12 cells has been visited — the inner body ran once for each. The outer loop is slow and steady; the inner loop is busy and quick.

Using both counters together: a times-table square

Nested loops get really useful when the inner body uses both counters at once. Here the outer counter a and the inner counter b are multiplied together, printing the whole times-table grid from 1 \times 1 up to 5 \times 5:

const size: number = 5; for (let a = 1; a <= size; a++) { // OUTER: which row of the table let row = ""; for (let b = 1; b <= size; b++) { // INNER: which column const product = a * b; // pad short numbers with a space so the columns line up neatly row = row + (product < 10 ? " " : "") + product + " "; } console.log(row); }

Every number in that square is a \times b for one particular pair of counters. The outer loop picks the row, the inner loop fills it in. Change size to 12 for the full times-table square you might have on your wall — one tiny pair of loops draws all 144 answers.

Mixing them: a loop with an if inside

Nesting isn't only loop-in-loop or if-in-if — you can freely mix them. A very common pattern is a loop that makes a decision on every pass. Here we go through the numbers 1 to 15 and, for each one, an if decides whether to shout that it's even or odd:

for (let n = 1; n <= 15; n++) { if (n % 2 === 0) { // % gives the remainder; 0 means it divides evenly console.log(n + " is even"); } else { console.log(n + " is odd"); } }

The loop repeats; the if inside it chooses freshly each time. This "loop, and decide something every pass" shape is one of the most useful in all of programming — filtering a list, checking each item, tallying things up. You'll reach for it again and again.

There's no limit — you can nest as deep as the problem needs. A loop over years, containing a loop over months, containing a loop over days would be three loops deep, and would run its innermost body \text{years} \times \text{months} \times \text{days} times. Every extra level just multiplies the work again. In practice programmers rarely go beyond two or three levels, because deeply nested code gets hard to read — and, as the "Watch out!" box explains, the amount of work can balloon frighteningly fast.

Here is the single most important thing to remember about nested loops. A loop of 10 inside a loop of 10 does not run 10 + 10 = 20 times. Its inner body runs 10 \times 10 = 100 times, because the inner 10 happens for every one of the outer 10:

\text{inner body runs} \;=\; (\text{outer passes}) \times (\text{inner passes}).

That multiplication is easy to underestimate. A loop of 1000 inside a loop of 1000 runs its body a million times — a computer can still do that quickly, but it's a million, not two thousand. It's a classic mistake to write an innocent-looking pair of loops and accidentally ask the computer to do far, far more work than you meant. Whenever you nest, pause and multiply: how many times does the very inside really run?

Because nested code has things inside things, it is dangerously easy to lose track of which loop or if you're inside. Two habits keep you safe:

Neat indentation and distinct counter names cost nothing and save you from hours of head-scratching.