Learn C Programming

Lesson 1 of 9 · Loops and Jump Statements

Module 6 · Loops and Jump Statements

while: the Idea of Repetition

FreeReading

In this lesson

  • Write a while loop with a start, a condition and a step, and trace it with a table first.
  • Read numbers until a sentinel arrives, or until the input ends, with while (scanf("%d", &x) == 1).
  • Explain what a missing part does, and keep the best value so far, starting from the first value read.

Bob's rocket game needs a countdown: 3, 2, 1, liftoff. He writes four printf lines and it works.

Then the designer asks for a countdown from 100. Bob does not want to write 101 lines, and he should not. One idea, "print the number, then make it smaller", has to run again and again.

A loop is a piece of code that runs again for as long as a condition allows. Computers never get bored of repeating, which is the whole reason they are useful.

while: the same block, again and again

A while loop tests a condition. If the condition is true, it runs the block once, then goes back and tests again. It stops the first time the test is false.

The while loop

while (condition) {
    statements
}
  • while is the keyword, and brackets always follow it.
  • condition is tested before every run of the block. Zero means stop; any other value means run once more.
  • The braces hold the body. One run of the body is called a pass (some books say an iteration).
  • When the test is false, the program carries on after the closing brace.
#include <stdio.h>

int main(void)
{
    int n = 3;

    while (n > 0) {
        printf("%d\n", n);
        n--;
    }
    printf("liftoff\n");
    return 0;
}
3
2
1
liftoff

Change the 3 to 100 for the long countdown. No other line changes.

So a while is an if that goes back to its own test after every pass. The body repeats until the answer turns into no.

Trace a loop with a table before you run it

The same lines run many times, so a loop is hard to read at a glance. Use a trace table (Module 5), one row per pass. Each row holds the variables at the test, the answer, and what the body did.

Passn at the testn > 0Body printsn after n--
13true32
22true21
31true10
none, test only0falsenothing; the loop ends0

Count the rows. The body ran 3 times, but the test ran 4 times. The last test, the one that says false, is the one that ends the loop.

The while loop as a flowchart, with its back edge Every pass ends with a jump back to the test start: n = 3 test: n > 0 yes body: print n step: n-- back edge no print liftoff The test runs before every pass. For n = 3 the body runs 3 times and the test runs 4 times. The only way out is the "no" arrow, so something in the body must move n towards it.

The highlighted arrow is the back edge: the jump from the end of the body back to the test. An if has none, and that arrow is the whole difference between a decision and a loop.

So fill in the table on paper before you run a loop. If the last row never arrives, the loop never ends.

Start, condition, step: the three parts of every loop

Every loop that ends has three parts. The start gives the variable its first value. The condition decides whether another pass runs. The step changes the variable, so the condition can become false.

PartIn the countdownIf it is missing or wrong
startint n = 3;The loop begins from the wrong place. Start n at 0 and the body runs zero times.
conditionn > 0Leave it out, while (), and nothing compiles. A condition that can never be false loops forever.
stepn--;n stays 3, the test is true every time, and the loop never ends.

The empty brackets are an error on every command line, the Playground included: error: expected expression before ')' token.

Note the zero-pass case. A while tests before the first pass, so its body may never run at all. Lesson 3 meets a loop that always runs at least once.

So when a loop misbehaves, check the three parts in order. Can the condition become false, and does the step move towards it?

Bob's timer that never ends

Bob writes a kitchen timer. It should count five seconds down and then beep. He rushes the step.

#include <stdio.h>

int main(void)
{
    int seconds = 5;

    while (seconds > 0) {
        seconds - 1;
    }
    printf("beep\n");
    return 0;
}

The line seconds - 1; works out 4 and throws it away, so seconds stays 5. The loop has no real step.

On the Playground nothing appears. After 10 seconds (the limit for a free or signed-out learner), the Playground stops the program. The status badge reads Time limit exceeded, and there is no output, not even the beep.

A judge, the program that grades problems against hidden tests, uses the same name. It usually allows 1 second per test. When it says Time limit exceeded, suspect a step that never moves towards false.

The Playground compiles Bob's timer in silence. A local gcc -Wall on GCC 12 says warning: statement with no effect [-Wunused-value]. The fix is seconds--;.

So a step has to store its result. A calculation that goes nowhere is not a step.

Stop at a sentinel: read until a 0 arrives

Maria's till reads prices, and the cashier types 0 when the basket is empty. A value like that 0, which means "stop" instead of "data", is a sentinel.

#include <stdio.h>

int main(void)
{
    int price = 0;
    int item = 0;

    while (scanf("%d", &price) == 1 && price != 0) {
        item++;
        printf("item %d: %d\n", item, price);
    }
    printf("%d items\n", item);
    return 0;
}
item 1: 120
item 2: 45
item 3: 300
3 items

That output is for the input 120 45 300 0 99. The 0 is the sentinel, and the 99 after it is never read.

The condition does two jobs, in order. First scanf reads a number and must return 1. Only then does price != 0 run, because && skips its right side when the left is false (Module 4).

The sentinel is read, fails the test, and never reaches the body. That is why "3 items" does not count the 0.

So a sentinel ends the loop and is never processed as data. Put the read and the sentinel check in the same condition.

Read until the input ends

Many judge problems have no sentinel. They say "numbers follow, up to the end of the input", and never say how many. That needs the pattern Module 3 promised.

#include <stdio.h>

int main(void)
{
    int mark = 0;
    int passed = 0;

    while (scanf("%d", &mark) == 1) {
        if (mark >= 40) {
            passed++;
        }
    }
    printf("%d passed\n", passed);
    return 0;
}
3 passed

That output is for the input 72 35 90 on one line and 40 12 on the next. scanf skips spaces and line breaks alike, so the layout of the input does not matter.

Alice's program does not know how many marks are coming. It reads while reading works. After the 12, scanf finds nothing and returns EOF, which is -1 (Module 3, lesson 5). -1 is not 1, so the loop ends.

On the Playground, the end of the input is simply the end of what you typed in the input box. A judge's test file ends the same way. In a terminal on your own computer, press Ctrl+D on Linux and macOS, or Ctrl+Z then Enter on Windows.

With an empty input box the body runs zero times, and the program prints 0 passed. That is the right answer for no marks.

So while (scanf("%d", &x) == 1) means "for every number there is". It also stops at the first thing that is not a number.

Keep the best so far, starting from the first value

Zara logs the temperature every morning and wants the coldest. A loop can keep the best answer so far in a variable, and improve it on each pass.

Her input starts with the number of days. The first reading becomes the coldest so far, because at that moment it is the only one.

#include <stdio.h>

int main(void)
{
    int days = 0;
    int t = 0;
    int coldest = 0;
    int day = 1;

    scanf("%d", &days);
    scanf("%d", &t);
    coldest = t;
    while (day < days) {
        scanf("%d", &t);
        if (t < coldest) {
            coldest = t;
        }
        day++;
    }
    printf("coldest: %d\n", coldest);
    return 0;
}
coldest: 12

That output is for the input 4, then 18 12 25 14. Here day counts the readings taken, and there is always at least one day.

Passday at the testday < dayst readcoldest after the pass
before the loop1not tested yet1818
11true1212
22true2512
33true1412
none, test only4falsenothing12

Why not start coldest at 0? Then no morning above freezing could beat it, and this input would print 0, a temperature nobody measured.

So a best-so-far variable starts from a real value, the first one read. Then the answer is always one of the inputs.

Braces on every loop body

Module 5 put braces on every if body, and loops follow the same rule. Without braces, a while owns exactly one statement.

#include <stdio.h>

int main(void)
{
    int n = 3;

    while (n > 0)
        printf("%d\n", n);
        n--;
    return 0;
}

The compiler sees one line in the loop, printf, so the program prints 3 again and again. The Playground is silent. A local gcc -Wall says warning: this 'while' clause does not guard... [-Wmisleading-indentation].

So every loop body in this track gets braces, even a body of one line. Braces, not spaces, tell C where a body ends.

Example 1: the smallest loop with a step that is not 1

The step can be any change that moves towards false. Here it doubles.

#include <stdio.h>

int main(void)
{
    int p = 1;

    while (p < 100) {
        printf("%d\n", p);
        p = p * 2;
    }
    return 0;
}
1
2
4
8
16
32
64

After 64 the step makes p 128, and 128 < 100 is false. The loop ran 7 passes, a number nobody wrote in the program.

Run in Compiler
Example 2: how many weeks until the goal

Kenji saves the same amount every week. Nobody knows the number of weeks in advance, which is when a while fits.

#include <stdio.h>

int main(void)
{
    int goal = 0;
    int weekly = 0;
    int saved = 0;
    int weeks = 0;

    scanf("%d %d", &goal, &weekly);
    while (saved < goal) {
        saved = saved + weekly;
        weeks++;
    }
    printf("%d weeks, %d saved\n", weeks, saved);
    return 0;
}
7 weeks, 1050 saved

That output is for the input 1000 150. Six weeks give 900, which is short, so a seventh pass runs. A goal of 0 gives 0 weeks, 0 saved: zero passes.

Check the step, too: a weekly amount of 0 adds nothing, and the loop never ends.

Run in Compiler
Example 3: the loop a beginner actually writes

Read every number there is, and sort each one into a count. Many first judge problems have this shape.

#include <stdio.h>

int main(void)
{
    int x = 0;
    int even = 0;
    int odd = 0;

    while (scanf("%d", &x) == 1) {
        if (x % 2 == 0) {
            even++;
        } else {
            odd++;
        }
    }
    printf("even %d\n", even);
    printf("odd %d\n", odd);
    return 0;
}
even 3
odd 2

That output is for the input 4 -7 10 3 0. The -7 is odd because its remainder, -1, is not 0 (Module 4). Here 0 is ordinary data, not a sentinel.

Run in Compiler

Where this is used

  • Counting a file. GNU coreutils' wc reads its input a block at a time in a loop. It stops when a read returns nothing: the end of the file.
  • Online judges. UVa problem 100, "The 3n + 1 problem", gives pairs of numbers up to the end of the file. The usual C solution reads them with while (scanf(...) == 2).
  • Game loops. id Software published Doom's source code in 1997. The game runs inside D_DoomLoop. That while (1) loop reads input and draws a frame on every pass.
  • Servers. Redis runs its event loop in aeMain as while (!eventLoop->stop), handling the ready connections on each pass.

Common mistakes

1. Testing scanf without the == 1.

while (scanf("%d", &x)) {
    printf("%d\n", x);
}

No message at any command line. At the end of the input scanf returns -1, which is not zero, so the test is true. For 7 9 it prints 7, then 9 forever. Compare with 1. You will write this because while (scanf(...)) reads like English.

2. A sentinel loop that ignores what scanf returned.

scanf("%d", &price);
while (price != 0) {
    printf("item: %d\n", price);
    scanf("%d", &price);
}

No message at any command line. If the input ends before a 0, the failed read leaves price as it was (Module 3). For 120 45 it prints item: 45 forever. Put the read in the condition. You will write this first because it is how the task sounds.

3. A semicolon after the while.

while (n > 0);
{
    printf("%d\n", n);
    n--;
}

Silent on the Playground. A local gcc -Wall gives the same -Wmisleading-indentation warning as above. The semicolon is an empty body, so the test repeats forever and the braces are never reached. Delete it.

4. The best so far starts at 0.

int coldest = 0;
while (day < days) {
    scanf("%d", &t);
    if (t < coldest) {
        coldest = t;
    }
    day++;
}

No message at any command line. For 18 12 25 14 it prints coldest: 0. Start from the first value read. You will write 0 because counters start at 0, but a best-so-far is not a counter.

Brain teaser

Bob found this on a forum, where someone called --> the "goes to" operator.

#include <stdio.h>

int main(void)
{
    int n = 3;

    while (n --> 0) {
        printf("%d\n", n);
    }
    printf("after the loop, n is %d\n", n);
    return 0;
}

It compiles with no message. What does it print, and what is --> really? The harder half: why is n not 0 after the loop?

Put a space in a different place. Then trace it with a table, and remember when n-- hands back its value (Module 4).

Exercise 1Easy

Bob's rocket game counts down from any number the player picks. Fix his countdown for real this time.

Input. One line with one integer n.

Output. The numbers from n down to 1, one per line.

Constraints. 1 <= n <= 1000.

Sample. Input 3 gives 3, 2 and 1 on three lines.

#include <stdio.h>

int main(void)
{
    int n = 0;
    scanf("%d", &n);

    /* Start, condition, step: one loop, one number per line. */

    return 0;
}

Not graded on its own. The problems lesson grades a while loop with harder edges in largest-input.

Run in Compiler
Exercise 2Easy

Maria's till now needs the bill. The cashier types the prices, then 0 when the basket is empty.

Input. Positive integers separated by spaces or newlines, then a 0. Anything after the 0 is ignored.

Output. One line with the sum of the prices before the 0.

Constraints. At most 1000 prices, each from 1 to 1000000, so the sum fits an int.

Sample. Input 120 45 300 0 gives 465. Input 0 gives 0.

#include <stdio.h>

int main(void)
{
    int price = 0;
    int total = 0;

    while (scanf("%d", &price) == 1 && price != 0) {
        /* Add this price to the total. */
    }

    /* Print the total. */

    return 0;
}

Not graded on its own. Its one trap, the 0 slipping into the total, shows up the moment you run the sample.

Run in Compiler
Exercise 3Medium

David's weather sensor writes readings to a log. He wants to know how many the log holds.

Input. Integers separated by spaces or newlines, up to the end of the input. There may be none at all.

Output. One line with the number of integers read.

Constraints. At most 100000 values, each from -1000000000 to 1000000000.

Sample. Input 5 -2 7 on one line and 0 3 on the next gives 5. An empty input gives 0.

#include <stdio.h>

int main(void)
{
    int x = 0;
    int count = 0;

    while (scanf("%d", &x) == 1) {
        /* One more reading. */
    }

    /* Print the count. An empty log must still print a number. */

    return 0;
}

Not graded on its own. The graded largest-input has to notice an empty input in the same way, with a harder answer to print.

Run in Compiler
Exercise 4Hard

Zara feeds the scoreboard a list of scores and asks for the best. Scores can be negative, and some games have no scores at all.

Input. Integers to the end of the input, possibly none.

Output. One line with the largest, or empty when there were no numbers.

Constraints. At most 100000 values, each from -1000000000 to 1000000000.

Sample. Input 3 -8 12 12 5 gives 12.

#include <stdio.h>

int main(void)
{
    int x = 0;

    while (scanf("%d", &x) == 1) {
        /* Keep the best score so far. What is it before any score arrives? */
    }

    /* Print the best score, or empty if nothing was read. */

    return 0;
}

Graded as largest-input. The hidden tests include a list of only negative scores and a completely empty input.

Run in Compiler

Common doubts

  • When do I use while, and when for?

    Use while when the number of passes is unknown in advance, as with input or a savings goal. The next lesson's for suits counting. Either can do the other's job; pick the one that reads more clearly.

  • Is while (1) always a bug?

    No. Doom's game loop is written that way on purpose. Such a loop leaves from inside, with break, which lesson 4 teaches. In your programs today, it is almost always a bug.

  • My program was stopped, and even the line it printed before the loop is missing. Why?

    printf collects output in a buffer and sends it on in pieces. On the Playground or a judge, the output goes to a file. A program that is stopped there never sends what was still waiting in the buffer.

Key takeaways

  • A while tests its condition before every pass, and runs zero passes if the first test is false.
  • A trace table has one row per pass, plus a last row where the test turns false.
  • Every loop needs a start, a condition and a step; without a real step, it never ends.
  • A quiet loop that never ends runs until the time limit stops it, and the badge reads Time limit exceeded.
  • while (scanf("%d", &x) == 1) reads every number there is; a sentinel is checked in the same condition.
  • A best-so-far starts from the first value read, and every loop body gets braces.

Next, for puts the start, the condition and the step on one line, and Bob tries to print 1 to 10.

End of lesson 1

Mark it done, and your progress moves with you.

Next: for: Init, Condition, Update, Traced