University College of Applied SciencesProgramming 1
العربية
Lecture 9 · 100 min

While Loops

How does a program repeat work without us repeating the code?

By the end of this lecture you will be able to

  1. Write a while loop with its three parts: initialise, test and update
  2. Trace the iterations of a loop in a table and with the debugger
  3. Use a counter and a running sum to calculate totals and averages
  4. Spot and fix infinite loops and off-by-one errors
  5. Read input until a sentinel value and validate input with do-while
  6. Solve digit problems, a guessing game and factorial with loops
In this lecture
  1. Printing 1 to 100 by hand means writing 100 lines
  2. Every while loop has three parts: initialise, test and update
  3. A trace table records what happens in each iteration
  4. The debugger shows you the loop turning, one line at a time
  5. A counter grows by one, a running sum adds each new value
  6. An average is the running sum divided by the counter, cast to double
  7. A loop whose test never becomes false never ends, and Stop ends it
  8. Off-by-one errors: the loop runs one time too many or too few
  9. A sentinel value tells the loop that the input is over
  10. do-while runs the body once first, then checks the test
  11. do-while suits input validation: ask, then ask again if the answer is wrong
  12. % 10 gives the last digit and / 10 removes it
  13. The same idea reverses a number and counts its digits
  14. A guessing game combines the loop, the condition and a counter
  15. Factorial starts at 1, because multiplying by 0 erases everything
  16. Review questions
Part one: the anatomy of a loop, and tracing it

Printing 1 to 100 by hand means writing 100 lines

What we know so far runs each line at most once. To print 1 to 100 we would need 100 nearly identical println statements, and one small change would mean editing 100 lines. A loop solves this: we write the work once and say when it stops.

Five lines instead of a hundred

Run the program and look at the output, then change 100 to 20 and run it again.

public class OneToHundred {
    public static void main(String[] args) {
        int number = 1;
        while (number <= 100) {
            System.out.println(number);
            number++;
        }
    }
}
Output
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100

Every while loop has three parts: initialise, test and update

Initialise

Gives the loop variable its first value before the loop starts. It runs once.

int count = 1;
Test

Checked before every iteration: if true the body runs, if false the loop ends.

while (count <= 3)
Update

Changes the loop variable inside the body, moving the test closer to false.

count++;

All three parts in one program

Predict the output before running. Then change the update to count += 2 and explain to a classmate why the output changed.

public class CountToThree {
    public static void main(String[] args) {
        int count = 1;                     // initialise
        while (count <= 3) {               // test
            System.out.println(count);
            count++;                       // update
        }
        System.out.println("Done");
    }
}
Output
1
2
3
Done

The test is checked before the first iteration too. What does this print?

public class NeverRuns {
    public static void main(String[] args) {
        int count = 10;
        while (count < 5) {
            System.out.println(count);
            count++;
        }
        System.out.println("Done");
    }
}
Show the output
Output
Done

Why? The test 10 < 5 is false from the start, so the body never runs and the program goes straight to the line after the loop.

A test must be true or false

We wanted to print 0, 1 and 2, but the compiler rejects the test. Fix it.

public class TestType {
    public static void main(String[] args) {
        int count = 0;
        while (count = 3) {
            System.out.println(count);
            count++;
        }
    }
}
Compiler messageincompatible types: int cannot be converted to boolean
Show the fix
public class TestType {
    public static void main(String[] args) {
        int count = 0;
        while (count < 3) {
            System.out.println(count);
            count++;
        }
    }
}
Output
0
1
2

Why? The = sign is assignment, so count = 3 has the value 3 rather than true or false, and a loop needs a boolean test such as count < 3.

A trace table records what happens in each iteration

Before we let the computer work, we do its work on paper: a column for each variable, a column for the test and a row for each iteration. This is the CountToThree program from the last section:

Iterationcount at the testcount <= 3Printscount after the update
11true12
22true23
33true34
Ends4falsenothingunchanged

Fill in the trace table. What does this print?

public class TraceByThree {
    public static void main(String[] args) {
        int i = 2;
        while (i < 12) {
            System.out.println(i);
            i += 3;
        }
        System.out.println("End: " + i);
    }
}
Show the output
Output
2
5
8
11
End: 14

Why? i grows by 3 each iteration: 2, 5, 8, 11. After printing 11 it becomes 14, so the test is false and the last line prints.

The debugger shows you the loop turning, one line at a time

  1. Press "Debug line by line"The program stops at the first line and highlights it. The highlighted line has not run yet.
  2. Press "Next line" or F8Before each press, say out loud which line will run next.
  3. Watch the boxThe box count appears, and its value changes at the update line.
  4. Watch the jumpAfter the last line of the body the highlight jumps back to the while line. This is where the looping really happens.

Trace the countdown

Trace the program to its end and count how many times the highlight stops on the while line. Then change 3 to 5 and predict the new number before you trace it.

public class Countdown {
    public static void main(String[] args) {
        int count = 3;
        while (count > 0) {
            System.out.println(count);
            count--;
        }
        System.out.println("Go!");
    }
}
Output
3
2
1
Go!

A table on paper, then the debugger

Fill in a table for total and steps on paper first, then trace the program and compare each row with the boxes. Where does your table differ from the debugger?

public class Doubling {
    public static void main(String[] args) {
        int total = 1;
        int steps = 0;
        while (total < 50) {
            total = total * 2;
            steps++;
        }
        System.out.println("total = " + total);
        System.out.println("steps = " + steps);
    }
}
Output
total = 64
steps = 6

A loop runs its body 5 times and then ends normally. How many times was its test checked?

Part two: counters and common errors

A counter grows by one, a running sum adds each new value

number4int
sum6int

These boxes show the program below as iteration four begins: we have added 1, 2 and 3, so the sum is 6. The sum starts at 0, because adding 0 changes nothing, and it builds up inside the loop with sum += number.

Add the numbers from 1 to 10

Trace the program and watch sum in each iteration. Then move int sum = 0; inside the loop and predict what happens: the compiler rejects the last line, because sum is no longer known outside the loop.

public class RunningSum {
    public static void main(String[] args) {
        int number = 1;
        int sum = 0;
        while (number <= 10) {
            sum += number;
            number++;
        }
        System.out.println("Sum = " + sum);
    }
}
Output
Sum = 55

A counter that does not grow every time: what does this print?

public class CountEvens {
    public static void main(String[] args) {
        int number = 1;
        int evens = 0;
        while (number <= 9) {
            if (number % 2 == 0) {
                evens++;
            }
            number++;
        }
        System.out.println(evens);
    }
}
Show the output
Output
4

Why? The loop turns 9 times, but evens grows only at 2, 4, 6 and 8. The number++ sits outside the if so that it changes every iteration.

Sum of the numbers up to n

Read a whole number n, then print the sum of the numbers from 1 to n. Print nothing else. If n is 0 the answer is 0.

import java.util.Scanner;

public class SumToN {
    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        int n = input.nextInt();
        // Add the numbers from 1 to n, then print the sum

    }
}
stdin
5
Expected output
15
Hint 1

You need two variables before the loop: a counter starting at 1 and a sum starting at 0.

Hint 2

The test is counter <= n, so the body never runs when n is 0.

Solution
import java.util.Scanner;

public class SumToN {
    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        int n = input.nextInt();
        int counter = 1;
        int sum = 0;
        while (counter <= n) {
            sum += counter;
            counter++;
        }
        System.out.println(sum);
    }
}

An average is the running sum divided by the counter, cast to double

The average of three grades

This lab reads fixed input: 80, 90 and 75. Trace the program and watch sum and count together. Then change count < 3 to count < 2 and predict the new average.

import java.util.Scanner;

public class AverageThree {
    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        int count = 0;
        int sum = 0;
        while (count < 3) {
            int grade = input.nextInt();
            sum += grade;
            count++;
        }
        double average = (double) sum / count;
        System.out.println("Sum: " + sum);
        System.out.println("Average: " + average);
    }
}
Output
Sum: 245
Average: 81.66666666666667

Remember integer division. What does each line print?

public class AverageCast {
    public static void main(String[] args) {
        int sum = 7;
        int count = 2;
        double wrong = sum / count;
        double right = (double) sum / count;
        System.out.println(wrong);
        System.out.println(right);
    }
}
Show the output
Output
3.0
3.5

Why? In sum / count both sides are int, so the decimal is dropped first and 3 is then stored as 3.0. The (double) cast happens before the division, so the decimal survives.

A loop whose test never becomes false never ends, and Stop ends it

If you forget the update line, count stays the same and the test stays true forever. This is an infinite loop, and it happens to every programmer. Do not fear it: on this site a Stop button appears while a program runs, so the loop ends and the page stays alive.

int count = 1;
while (count <= 5) {
    System.out.println(count);
}
An infinite loop: where is the update?

Write an infinite loop on purpose

This program ends as it stands. Type the fragment above inside main in place of its current lines, press Run, watch the numbers pour out, then press Stop. Afterwards press Reset to get the original program back.

public class LoopPlayground {
    public static void main(String[] args) {
        int count = 1;
        while (count <= 5) {
            System.out.println(count);
            count++;
        }
    }
}
Output
1
2
3
4
5

A line the program can never reach

The compiler sometimes spots an infinite loop by itself. Fix the program so it prints Hi three times and then Done.

public class Unreachable {
    public static void main(String[] args) {
        while (true) {
            System.out.println("Hi");
        }
        System.out.println("Done");
    }
}
Compiler messageunreachable statement
Show the fix
public class Unreachable {
    public static void main(String[] args) {
        int count = 0;
        while (count < 3) {
            System.out.println("Hi");
            count++;
        }
        System.out.println("Done");
    }
}
Output
Hi
Hi
Hi
Done

Why? The test true never changes, so the program never leaves the loop, which is why the compiler says the line after it is unreachable. The problem is the test, not the line the message points at.

Off-by-one errors: the loop runs one time too many or too few

This is the most famous loop mistake: the program runs and the compiler has no complaint, but the number of iterations is wrong by one. The cause is usually < instead of <=, or a wrong starting value.

StartTestValues in the bodyIterations
i = 1i <= 51 2 3 4 55
i = 1i < 51 2 3 44
i = 0i < 50 1 2 3 45
i = 0i <= 50 1 2 3 4 56

The program runs, but it is wrong

The goal is the sum of the numbers from 1 to 5, which is 15, but the program prints 10. Trace it, find the missing iteration, then fix the test and check that the output becomes 15.

public class OffByOne {
    public static void main(String[] args) {
        int i = 1;
        int sum = 0;
        while (i < 5) {
            sum += i;
            i++;
        }
        System.out.println(sum);
    }
}
Output
10

How many times does the body of this loop run?

int i = 0;
while (i <= 10) {
    i += 2;
}
int i = 1;
while (i <= 5);
{
    i++;
}
One more mistake: a semicolon after the test makes a silent infinite loop. Type this fragment inside main in the lab above, in place of its current lines; the program prints nothing, and Stop is the way out.
Part three: input and digit problems

A sentinel value tells the loop that the input is over

We may not know in advance how many numbers the user will enter. The fix: agree on a special value that cannot be real data, such as -1, meaning "I am finished". This is a sentinel value.

Type it yourself

Run the program, enter a few numbers, then enter -1. Try entering -1 first: what does it print?

import java.util.Scanner;

public class SentinelSum {
    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        int sum = 0;
        System.out.print("Number (-1 to stop): ");
        int number = input.nextInt();
        while (number != -1) {
            sum += number;
            System.out.print("Number (-1 to stop): ");
            number = input.nextInt();
        }
        System.out.println("Sum: " + sum);
    }
}

The input here is 4, 10, 6, then -1. Does -1 join the sum or the count?

import java.util.Scanner;

public class SentinelCount {
    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        int sum = 0;
        int count = 0;
        int number = input.nextInt();
        while (number != -1) {
            sum += number;
            count++;
            number = input.nextInt();
        }
        System.out.println(count + " numbers, sum " + sum);
    }
}
Show the output
Output
3 numbers, sum 20

Why? When -1 is read the test runs next, so the loop ends before the value reaches sum or count. That is why the read is the last line of the body.

Average until the sentinel

Read whole numbers until -1, without printing any prompts. Print Count: and how many there were, then on the next line Average: and their average as a double. If the first input is -1, print only No numbers.

import java.util.Scanner;

public class SentinelAverage {
    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        // Read until -1, keeping a sum and a count

    }
}
stdin
80
90
70
-1
Expected output
Count: 3
Average: 80.0
Hint 1

Start from the pattern in the SentinelCount program above.

Hint 2

After the loop use if (count == 0) so that you never divide by zero.

Solution
import java.util.Scanner;

public class SentinelAverage {
    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        int sum = 0;
        int count = 0;
        int number = input.nextInt();
        while (number != -1) {
            sum += number;
            count++;
            number = input.nextInt();
        }
        if (count == 0) {
            System.out.println("No numbers");
        } else {
            double average = (double) sum / count;
            System.out.println("Count: " + count);
            System.out.println("Average: " + average);
        }
    }
}

do-while runs the body once first, then checks the test

while: test, then run

The body may never run.

while (test) {
    body;
}
do-while: run, then test

The body runs at least once. Note the semicolon at the end.

do {
    body;
} while (test);

The same test and the same start. What does this print?

public class WhileVsDo {
    public static void main(String[] args) {
        int a = 10;
        while (a < 5) {
            System.out.println("while " + a);
            a++;
        }
        int b = 10;
        do {
            System.out.println("do " + b);
            b++;
        } while (b < 5);
    }
}
Show the output
Output
do 10

Why? The test is false in both cases. But while tests first and prints nothing, while do-while runs the body once, then tests and stops.

Trace the difference

Trace the program and notice that the highlight enters the body directly and that the while line comes at the bottom. Then change the start to n = 100 and predict the output.

public class DoWhileTrace {
    public static void main(String[] args) {
        int n = 1;
        do {
            System.out.println(n);
            n = n * 3;
        } while (n < 20);
    }
}
Output
1
3
9

A variable the test cannot see

We declared n inside the body, and the while line is outside the braces. Fix the program so it prints 3, 2, then 1.

public class DoScope {
    public static void main(String[] args) {
        do {
            int n = 3;
            System.out.println(n);
            n--;
        } while (n > 0);
    }
}
Compiler messagecannot find symbol
Show the fix
public class DoScope {
    public static void main(String[] args) {
        int n = 3;
        do {
            System.out.println(n);
            n--;
        } while (n > 0);
    }
}
Output
3
2
1

Why? A variable declared inside { } does not exist outside them, and the while line of a do-while is outside them. Moving the declaration before do also fixes a second bug: inside the body, n would go back to 3 on every iteration.

do-while suits input validation: ask, then ask again if the answer is wrong

We must ask the user at least once, and only after the answer do we know whether to ask again. That is exactly how do-while behaves. The test describes the rejected answer, so we keep asking while it is wrong.

Try to fool the program

Run the program and enter 150, then -3, then a valid grade. Try the limits 0 and 100 too. Then swap || for && and predict the result: if the program now accepts everything, why?

import java.util.Scanner;

public class ValidateGrade {
    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        int grade;
        do {
            System.out.print("Grade (0-100): ");
            grade = input.nextInt();
        } while (grade < 0 || grade > 100);
        System.out.println("Accepted: " + grade);
    }
}

We want to keep asking until the user enters a positive number. What is the right test in while?

A rating from 1 to 5

Print Rating (1-5): using print and read a number, repeating until the number is between 1 and 5. Then print Thanks, you chose followed by the number. Note: what you type does not appear in the test output, so the prompts run together.

import java.util.Scanner;

public class ValidRating {
    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        int rating;
        // Ask with a do-while loop until the rating is valid
        rating = 0;
        System.out.println("Thanks, you chose " + rating);
    }
}
stdin
9
0
4
Expected output
Rating (1-5): Rating (1-5): Rating (1-5): Thanks, you chose 4
Hint 1

Delete the rating = 0; line and write the loop in its place.

Hint 2

The test describes rejection: rating < 1 || rating > 5.

Solution
import java.util.Scanner;

public class ValidRating {
    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        int rating;
        do {
            System.out.print("Rating (1-5): ");
            rating = input.nextInt();
        } while (rating < 1 || rating > 5);
        System.out.println("Thanks, you chose " + rating);
    }
}

% 10 gives the last digit and / 10 removes it

These two operators take any number apart: take the last digit, remove it and repeat until the number is 0. This is the trace table for 472:

Iterationn at the testn % 10sum after addingn after n / 10
14722247
247794
344130
Ends0not computed130

Sum of the digits

Trace the program and compare each iteration with the table above. Then change the number to 9075 and write its table before running.

public class SumOfDigits {
    public static void main(String[] args) {
        int n = 472;
        int sum = 0;
        while (n > 0) {
            int digit = n % 10;
            sum += digit;
            n = n / 10;
        }
        System.out.println(sum);
    }
}
Output
13

How many times does the digit 7 appear in the number?

public class CountSevens {
    public static void main(String[] args) {
        int n = 7170;
        int sevens = 0;
        while (n > 0) {
            if (n % 10 == 7) {
                sevens++;
            }
            n = n / 10;
        }
        System.out.println(sevens);
    }
}
Show the output
Output
2

Why? The loop reads digits from the right: 0, 7, 1, 7, so the counter grows twice.

The same idea reverses a number and counts its digits

Reverse the number

The statement reversed = reversed * 10 + digit shifts the digits left and puts the new one in the units place. Trace the program and watch the box reversed: 3, then 32, then 321. Then try 120 and explain the output.

public class ReverseNumber {
    public static void main(String[] args) {
        int n = 123;
        int reversed = 0;
        while (n > 0) {
            int digit = n % 10;
            reversed = reversed * 10 + digit;
            n = n / 10;
        }
        System.out.println(reversed);
    }
}
Output
321

Count the digits

Read a non-negative whole number, then print how many digits it has. Print nothing else. Note that the number 0 has one digit.

import java.util.Scanner;

public class CountDigits {
    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        int n = input.nextInt();
        // Count the digits of n, then print the count

    }
}
stdin
4096
Expected output
4
Hint 1

In each iteration remove a digit with n = n / 10 and grow the counter.

Hint 2

Use do-while with the test n > 0 so that the zero case works.

Solution
import java.util.Scanner;

public class CountDigits {
    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        int n = input.nextInt();
        int count = 0;
        do {
            count++;
            n = n / 10;
        } while (n > 0);
        System.out.println(count);
    }
}

A guessing game combines the loop, the condition and a counter

The secret here is a fixed number in the code, so we can focus on the loop logic and test it. We do not know the number of attempts in advance, so the loop continues while the answer is wrong.

Play

Run the program and guess the number, which is between 1 and 100. Then change the secret and play with a classmate: what is the fewest attempts that guarantees a win if you always start from the middle?

import java.util.Scanner;

public class GuessGame {
    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        int secret = 42;
        int guess;
        do {
            System.out.print("Your guess: ");
            guess = input.nextInt();
            if (guess < secret) {
                System.out.println("Too low");
            } else if (guess > secret) {
                System.out.println("Too high");
            }
        } while (guess != secret);
        System.out.println("Correct!");
    }
}

Count the attempts

The secret is 7. Read guesses without printing prompts: print Too low or Too high after each wrong guess. On a correct guess, print Correct! Attempts: followed by the number of attempts.

import java.util.Scanner;

public class GuessAttempts {
    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        int secret = 7;
        // Keep reading guesses, and count them

    }
}
stdin
3
9
7
Expected output
Too low
Too high
Correct! Attempts: 3
Hint 1

Grow the attempts counter in every iteration, whether the guess is right or wrong.

Hint 2

Print the winning message after the loop, not inside it.

Solution
import java.util.Scanner;

public class GuessAttempts {
    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        int secret = 7;
        int attempts = 0;
        int guess;
        do {
            guess = input.nextInt();
            attempts++;
            if (guess < secret) {
                System.out.println("Too low");
            } else if (guess > secret) {
                System.out.println("Too high");
            }
        } while (guess != secret);
        System.out.println("Correct! Attempts: " + attempts);
    }
}

Factorial starts at 1, because multiplying by 0 erases everything

The factorial 5! means 5 × 4 × 3 × 2 × 1 = 120, and 0! is 1 by definition. The pattern is the running sum again, multiplying instead of adding and starting from 1 instead of 0.

We started the result at 0, as we do for a sum. What does this print?

public class ZeroStart {
    public static void main(String[] args) {
        int n = 4;
        int result = 0;
        while (n > 0) {
            result = result * n;
            n--;
        }
        System.out.println(result);
    }
}
Show the output
Output
0

Why? Anything multiplied by 0 is 0, so result stays zero in every iteration. Start a product at 1 and a sum at 0.

The box fills up again

Run the program: the output is wrong, because 13! is larger than the biggest int. Change the type of result to long, run it again and compare.

public class FactorialOverflow {
    public static void main(String[] args) {
        int n = 13;
        int result = 1;
        while (n > 0) {
            result = result * n;
            n--;
        }
        System.out.println(result);
    }
}
Output
1932053504

Calculate the factorial

Read a whole number n between 0 and 20, then print its factorial. Use the type long. Print nothing else.

import java.util.Scanner;

public class FactorialN {
    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        int n = input.nextInt();
        // Multiply the result by n, n - 1, ... 1

    }
}
stdin
5
Expected output
120
Hint 1

Start with long result = 1;.

Hint 2

The loop while (n > 0) does not run at 0, so the result stays 1 as we want.

Solution
import java.util.Scanner;

public class FactorialN {
    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        int n = input.nextInt();
        long result = 1;
        while (n > 0) {
            result = result * n;
            n--;
        }
        System.out.println(result);
    }
}

Review questions

What happens if you forget the update part of a while loop?

When do you prefer do-while over while?

Why do we choose -1 as the sentinel in a list of grades?

Trace on paper, then check with the debugger. What does this print?

public class ReviewTrace {
    public static void main(String[] args) {
        int x = 20;
        int steps = 0;
        while (x > 1) {
            x = x / 2;
            steps++;
        }
        System.out.println(x + " " + steps);
    }
}
Show the output
Output
1 4

Why? x goes 20, 10, 5, 2, 1, with the decimal dropped in 5 / 2. That is four iterations, and at 1 the test is false.