---
title: "7.4 loop and while"
description: "Rust's while loop, the dedicated loop keyword for infinite loops, the do-while idiom, and the unsigned countdown trap."
url: "https://learnrust.net/chapter-7/loop-and-while/"
last_updated: "2026-06-12"
---

# 7.4 loop and while

Conditionals choose a path once. Loops walk a path repeatedly, and they're the reason a twelve-line program can do a billion things. Rust has three loop statements; this lesson covers `while` and `loop`, and the next covers `for`.

## while

A **while loop** checks a condition, and if it's `true`, runs its block, then checks again. And again. The loop ends the first time the check comes up `false`:

```rust
fn main() {
    let mut count = 1;
    while count <= 10 {
        print!("{count} ");
        count += 1;
    }
    println!("done!");
}
```

```
1 2 3 4 5 6 7 8 9 10 done!
```

(`print!` is `println!` minus the newline, so the numbers land on one line.)

Trace it like the machine does. `count` is 1; `1 <= 10` is true; print, bump `count` to 2; check again; ... ; print 10, bump to 11; `11 <= 10` is false; the loop is over and execution continues below it. Each full pass through the block is called an **iteration**; this loop ran ten of them.

The condition is checked *before* every iteration, including the first. So a while loop whose condition starts out false runs zero times:

```rust
let mut count = 15;
while count <= 10 {   // false on arrival; body never runs
    count += 1;
}
```

The usual anatomy is the one in the first example: a counter initialized before the loop, a condition that tests it, and an update inside the block. Forget the update and the condition never changes, which means the loop never ends. This is called an **infinite loop**, and writing one by accident is a rite of passage (your program just... sits there; Ctrl-C in the terminal ends it). When it happens, check the update first.

## Deliberate infinite loops: loop

Sometimes forever is the point. A game keeps taking turns until somebody wins; a server answers requests until it's shut down. Neither knows at the top of any iteration whether it's the last. The C-family idiom is `while (true)`. Try the equivalent here and rustc has an opinion:

```rust
fn main() {
    while true {
        println!("again!");
    }
}
```

```
warning: denote infinite loops with `loop { ... }`
 --> src/main.rs:2:5
  |
2 |     while true {
  |     ^^^^^^^^^^ help: use `loop`
  |
  = note: `#[warn(while_true)]` on by default
```

Rust has a dedicated keyword for the intentional infinite loop:

```rust
loop {
    println!("again!");   // runs until something inside stops it
}
```

This isn't compiler pedantry about spelling. `while true` is a while loop whose condition *happens* to be constant; `loop` is a promise that there is no condition. The compiler takes the promise seriously: it knows a `loop` can only be left deliberately, which unlocks abilities `while true` doesn't get (lesson 7.6 cashes this in). For now, the rule is simple:

> **Best practice**
>
> Write intentional infinite loops with `loop`, never `while true`. It states the intent, and the compiler rewards the honesty.

Of course, "infinite" loops almost always end eventually; they just decide from the *inside*. The escape hatch is the `break` statement, which immediately ends the loop it's in. Here's the everyday shape, a menu that re-asks until the answer makes sense:

```rust
use std::io;

fn main() {
    loop {
        println!("Please pick an option (1-4):");
        let mut input = String::new();
        io::stdin().read_line(&mut input).expect("failed to read line");
        let choice: u32 = input.trim().parse().expect("please type a number");
        if choice >= 1 && choice <= 4 {
            println!("you picked option {choice}");
            break;
        }
        println!("{choice} is not on the menu, try again.");
    }
    println!("goodbye!");
}
```

```
Please pick an option (1-4):
7
7 is not on the menu, try again.
Please pick an option (1-4):
2
you picked option 2
goodbye!
```

The reading-and-parsing lines are lesson [5.6](https://learnrust.net/chapter-5/parsing-strings-into-numbers/)'s idiom, working for a living. `break` gets its full lesson at 7.6; for now, "ends the loop, immediately" is the whole story.

One more thing this example settles. C++ has a third loop, `do-while`, whose condition sits at the *bottom*, for exactly this run-at-least-once situation (you can't test the choice before you've asked for it). Rust doesn't have do-while, and the example shows why nobody misses it: `loop` plus a conditional `break` is the same machine, with the exit condition placed wherever it actually belongs, top, bottom, or middle.

## The unsigned countdown trap

Now a counting loop that goes *down*, with a bug, and the type system has opinions. Suppose we're counting down to liftoff and, mindful of lesson [4.2](https://learnrust.net/chapter-4/integer-types/), we pick `u32` since a countdown is never negative:

```rust
fn main() {
    let mut i: u32 = 3;
    while i >= 0 {
        println!("{i}");
        i -= 1;
    }
    println!("liftoff!");
}
```

The plan: print 3, 2, 1, 0, then `i` becomes -1, the condition fails, liftoff. The compiler spots the hole in that plan before running a single line:

```
warning: comparison is useless due to type limits
 --> src/main.rs:3:11
  |
3 |     while i >= 0 {
  |           ^^^^^^
  |
  = note: `#[warn(unused_comparisons)]` on by default
```

A `u32` is *always* `>= 0`; the condition can never fail; this loop is infinite, and rustc said so politely. Run it anyway and the second shoe drops: after printing 0, the `i -= 1` is the overflow from lesson [4.4](https://learnrust.net/chapter-4/integer-overflow/), and the debug build panics with `attempt to subtract with overflow` rather than wrapping `i` around to 4,294,967,295 and counting down from there (which is what the same bug does, silently, in C++).

The fix is to stop *before* the subtraction goes wrong, and to make the loop's last useful iteration explicit:

```rust
fn main() {
    let mut i: u32 = 3;
    while i > 0 {
        println!("{i}");
        i -= 1;
    }
    println!("liftoff!");
}
```

```
3
2
1
liftoff!
```

(If you want the 0 printed before liftoff, print first and ask questions later: condition `i > 0` with the print moved, or just use an `i32`. The point isn't the cosmetics; it's that `while i >= 0` on an unsigned counter is always a bug, and here it's a *labeled* one.)

## Loops in loops

A loop's block is just a block, so it can contain another loop. The inner loop runs to completion on every iteration of the outer one:

```rust
fn main() {
    let mut outer = 1;
    while outer <= 5 {
        let mut inner = 1;
        while inner <= outer {
            print!("{inner} ");
            inner += 1;
        }
        println!();
        outer += 1;
    }
}
```

```
1 
1 2 
1 2 3 
1 2 3 4 
1 2 3 4 5 
```

Note where `inner` is declared: *inside* the outer loop, so it's recreated at 1 for every row. Declare it once above both loops and it would keep its value between rows, printing one ever-lengthening line. (This is lesson [1.11](https://learnrust.net/chapter-1/expressions-and-statements/)'s scoping rules again, doing useful work: the tightest scope isn't just tidy, it's the correct behavior.)

## Quiz time

**Question #1**

How many iterations does this loop run, and what does it print?

```rust
fn main() {
    let mut n = 1;
    while n < 100 {
        n *= 2;
    }
    println!("{n}");
}
```

<details class="solution">
<summary>Show solution</summary>

```
128
```

Seven iterations: `n` doubles through 2, 4, 8, 16, 32, 64, 128, and the check `128 < 100` fails. Note the printed value is the first power of two *past* the limit, not the last one under it; loops end one step after their condition stops being true, a classic source of off-by-one thinking.

</details>

**Question #2**

Write a program that prints the lowercase alphabet with character codes, one per line: `a = 97` through `z = 122`. Use a while loop. (Hint: `b'a'` is a byte literal, the `u8` code of `'a'`, and lesson [4.10](https://learnrust.net/chapter-4/numeric-conversions/)'s `as` can turn a code back into a `char`.)

<details class="solution">
<summary>Show solution</summary>

```rust
fn main() {
    let mut code = b'a';
    while code <= b'z' {
        println!("{} = {}", code as char, code);
        code += 1;
    }
}
```

The first lines of output:

```
a = 97
b = 98
c = 99
```

and so on, 26 lines in all, ending with `z = 122`. The counter *is* the character code; we cast it to `char` only for display. (Why stop at `b'z'` and not worry about overflow? 122 is comfortably inside `u8`'s range, and the loop ends there. Past `b'z'`, the `+= 1` would be fine right up until 255.)

</details>

**Question #3**

Modify the nested-loop example to print the triangle upside down: first row `1 2 3 4 5`, last row `1`.

<details class="solution">
<summary>Show solution</summary>

```rust
fn main() {
    let mut outer = 5;
    while outer >= 1 {
        let mut inner = 1;
        while inner <= outer {
            print!("{inner} ");
            inner += 1;
        }
        println!();
        outer -= 1;
    }
}
```

```
1 2 3 4 5 
1 2 3 4 
1 2 3 
1 2 
1 
```

The outer counter runs 5 down to 1 (safely: it's an `i32` by inference, and the condition `>= 1` stops before anything goes negative even for unsigned types). The inner loop is unchanged; the rows shrink because their *limit* shrinks.

</details>

Next: the loop that counts for you. `for`, ranges, and the end of six irritating lines from chapter 4.

## Sitemap

See the full [sitemap](https://learnrust.net/sitemap.md) for all pages.
