Control flow · about 55 min

break, continue, nested loops and loop patterns

Why this matters

Real loops rarely run cleanly from start to finish. A search stops as soon as it finds what it is looking for. Input processing skips blank or invalid records. A grid or a table needs one loop inside another. This lesson covers the two statements that alter a loop's flow, break and continue, how loops nest, and a handful of patterns (accumulators, flags, sentinels, running minimum and maximum) that appear in almost every program you will write.

break: leave the loop now

break immediately exits the innermost enclosing loop (or switch). Execution continues with the first statement after that loop.

int n = 91;
int divisor = 0;
for (int d = 2; d * d <= n; d++) {
    if (n % d == 0) {
        divisor = d;
        break;            /* found one: no point testing further */
    }
}
if (divisor)
    printf("%d = %d x %d\n", n, divisor, n / divisor);
else
    printf("%d is prime\n", n);
91 = 7 x 13

Without break, the loop would keep testing after the answer was known. In a search over a million items, stopping early is the difference between fast and slow.

continue: skip to the next pass

continue abandons the rest of the current pass and goes on to the next one. In a while or do-while it jumps to the condition test; in a for it jumps to the update expression first and then to the test.

for (int i = 1; i <= 10; i++) {
    if (i % 3 == 0)
        continue;         /* skip multiples of 3 */
    printf("%d ", i);
}
printf("\n");
1 2 4 5 7 8 10
  for (init; cond; update) {         while (cond) {
      ...                                ...
      continue; --+                      continue; --+
      ...         |                      ...         |
  }               |                  }               |
     goes to update, then cond          goes straight to cond

That difference is the one place where a for loop is not identical to its while rewrite: in the while version, the update at the bottom of the body is skipped by continue, which can create an infinite loop:

int i = 0;
while (i < 10) {
    if (i % 3 == 0)
        continue;         /* BUG: i++ below is skipped, i stays 0 forever */
    printf("%d ", i);
    i++;
}

continue shines as a guard at the top of a loop body: "if this item is not interesting, move on". It keeps the main logic unindented instead of wrapping it in a large if.

Nested loops

A loop body can contain another loop. The inner loop runs completely for each pass of the outer loop:

for (int row = 1; row <= 3; row++) {
    for (int col = 1; col <= 4; col++)
        printf("%4d", row * col);
    printf("\n");
}
   1   2   3   4
   2   4   6   8
   3   6   9  12

The inner body runs 3 x 4 = 12 times. Nesting multiplies work: two nested loops over n items do n squared steps, three do n cubed. For n = 1000 that is a million versus a billion steps, which is why the shape of your loops matters more than micro-optimizations.

break only leaves one level

for (int i = 0; i < 3; i++) {
    for (int j = 0; j < 3; j++) {
        if (j == 1)
            break;        /* leaves the j loop only */
        printf("(%d,%d) ", i, j);
    }
}
(0,0) (1,0) (2,0)

To stop both loops, the usual tools are a flag variable tested by the outer loop:

int found = 0;
for (int a = 1; a < 20 && !found; a++) {
    for (int b = a; b < 20; b++) {
        if (a * a + b * b == 13 * 13) {
            printf("%d^2 + %d^2 = 13^2\n", a, b);
            found = 1;
            break;
        }
    }
}
5^2 + 12^2 = 13^2

or moving the loops into their own function and using return (functions are the next module), or goto:

for (...) {
    for (...) {
        if (done)
            goto out;
    }
}
out:
    ...

goto jumps to a label in the same function. Its reputation is poor because unrestricted jumping makes code impossible to follow, but this one use, escaping nested loops or jumping forward to a cleanup block, is accepted style in C and common in the Linux kernel.

Loop patterns

Most loops are one of a few patterns. Recognizing them lets you write loops quickly and correctly.

Counter: how many items satisfy a condition.

int evens = 0;
for (int i = 1; i <= 20; i++)
    if (i % 2 == 0)
        evens++;

Accumulator: combine every item into one value (sum, product). Start at the identity: 0 for a sum, 1 for a product.

long product = 1;
for (int i = 1; i <= 10; i++)
    product *= i;          /* 10! = 3628800 */

Search with early exit: the prime test above. A flag records whether you found it.

Running minimum and maximum: initialize from the first item, not from an arbitrary constant. If you start min at 0 and all inputs are positive, min stays 0 forever.

if (count == 0 || x < min)
    min = x;

Sentinel-controlled input: keep reading until a special value that is not data (like 0 or -1) or the end of input:

while (scanf("%d", &x) == 1) {
    if (x == 0)
        break;            /* sentinel */
    ...
}

Checking scanf's return value inside the loop condition means the loop also stops cleanly at end of input or on non-numeric input. The next lesson builds on exactly this.

Common mistakes

Recap

Going further: write a program that prints all primes below 100 using a nested loop and break. Then count how many times the inner loop body runs, and see how much the d * d <= n bound saves compared with testing every d < n.

Exercises

Exercise 1 · quiz

Which loop does break leave?

What does this print?

for (int i = 0; i < 3; i++) {
    for (int j = 0; j < 3; j++) {
        if (j == i)
            break;
        printf("%d%d ", i, j);
    }
}
printf("\n");
Answer
  • 10 20 21

break leaves only the innermost loop that contains it; the outer loop carries on with its next pass. For i = 0, j = 0 equals i at once, so nothing prints. For i = 1, it prints 10, then j = 1 breaks. For i = 2 it prints 20 and 21, then breaks at j = 2. Effectively, the inner loop runs while j < i.

Exercise 2 · write code

Is it prime?

Read an integer n and print n is prime or n is not prime. Numbers less than 2 are not prime. Test divisors from 2 upward and stop at the first one that divides n evenly, using break. (You only need to test divisors d with d * d <= n: if n has a divisor larger than its square root, it also has one smaller.)

File: main.c

Starting point

#include <stdio.h>

int main(void)
{
    int n;
    if (scanf("%d", &n) != 1)
        return 1;
    int prime = 1;      /* assume prime until a divisor is found */
    /* search for a divisor here */
    if (prime)
        printf("%d is prime\n", n);
    else
        printf("%d is not prime\n", n);
    return 0;
}

Example

input

97

expected output

97 is prime

Example

input

91

expected output

91 is not prime
Hint 1

Handle n < 2 first: set prime to 0.

Hint 2

Loop for (int d = 2; d * d <= n; d++); if n % d == 0, set prime = 0 and break.

Reference solution
#include <stdio.h>

int main(void)
{
    int n;
    if (scanf("%d", &n) != 1)
        return 1;
    int prime = 1;      /* assume prime until a divisor is found */
    if (n < 2)
        prime = 0;
    for (int d = 2; d * d <= n; d++) {
        if (n % d == 0) {
            prime = 0;
            break;
        }
    }
    if (prime)
        printf("%d is prime\n", n);
    else
        printf("%d is not prime\n", n);
    return 0;
}

Exercise 3 · write code

A triangle of stars

Read a height h and print a right-aligned triangle of * characters with h rows. Row r (counting from 1) has h - r spaces followed by r stars, and no trailing spaces. For h = 4:

   *
  **
 ***
****

If h is 0 or negative, print nothing.

File: main.c

Starting point

#include <stdio.h>

int main(void)
{
    int h;
    if (scanf("%d", &h) != 1)
        return 1;
    for (int r = 1; r <= h; r++) {
        /* print spaces, then stars, then a newline */
    }
    return 0;
}

Example

input

4

expected output

   *
  **
 ***
****

Example

input

1

expected output

*
Hint 1

Inside the row loop you need two separate inner loops, one after the other.

Hint 2

putchar(' ') prints one space and putchar('*') one star.

Reference solution
#include <stdio.h>

int main(void)
{
    int h;
    if (scanf("%d", &h) != 1)
        return 1;
    for (int r = 1; r <= h; r++) {
        for (int s = 0; s < h - r; s++)
            putchar(' ');
        for (int s = 0; s < r; s++)
            putchar('*');
        putchar('\n');
    }
    return 0;
}

Exercise 4 · write code

Statistics with a sentinel

Read integers until the value 0 (the sentinel, which is not part of the data) or the end of input. Negative numbers are invalid readings: skip them with continue. For the remaining (positive) numbers, print one line:

count=4 sum=30 min=2 max=12

If there were no positive numbers, print no data instead.

For input 5 -3 12 2 -1 11 0 99 the output is count=4 sum=30 min=2 max=12 (the 99 after the sentinel is never read).

File: main.c

Starting point

#include <stdio.h>

int main(void)
{
    int x;
    int count = 0, sum = 0, min = 0, max = 0;
    while (scanf("%d", &x) == 1) {
        /* stop at 0, skip negatives, update the statistics */
    }
    printf("count=%d sum=%d min=%d max=%d\n", count, sum, min, max);
    return 0;
}

Example

input

5 -3 12 2 -1 11 0 99

expected output

count=4 sum=30 min=2 max=12

Example

input

7
3

expected output

count=2 sum=10 min=3 max=7
Hint 1

if (x == 0) break; then if (x < 0) continue; at the top of the loop body.

Hint 2

Starting min at 0 is wrong: no positive number is smaller. Use the first value as the starting min and max (when count is still 0).

Hint 3

After the loop, check count == 0 before printing the statistics.

Reference solution
#include <stdio.h>

int main(void)
{
    int x;
    int count = 0, sum = 0, min = 0, max = 0;
    while (scanf("%d", &x) == 1) {
        if (x == 0)
            break;
        if (x < 0)
            continue;
        if (count == 0 || x < min)
            min = x;
        if (count == 0 || x > max)
            max = x;
        sum += x;
        count++;
    }
    if (count == 0) {
        printf("no data\n");
        return 0;
    }
    printf("count=%d sum=%d min=%d max=%d\n", count, sum, min, max);
    return 0;
}