Control Flow

Reviewed & published by Brayan K

Rust is a systems programming language focused on speed, memory safety, and fearless concurrency — and control flow is how your program makes decisions and repeats work.

Part of the free Rust course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.

In this lesson you'll branch with if/else (which are expressions), and repeat with loop, while, and for.

What You'll Learn in This Lesson

1️⃣ Branching with if / else

An if runs a block when its condition is true, and the condition must be a bool — Rust won't treat numbers as truthy. Because if is an expression, you can assign its result directly to a variable, which replaces the ternary operator from other languages. Both branches must produce the same type.

fn main() {
    let number = 7;

    // if conditions must be a bool. There are no parentheses needed.
    if number % 2 == 0 {
        println!("{} is even", number);
    } else {
        println!("{} is odd", number);     // -> 7 is odd
    }

    // if is an EXPRESSION, so it can produce a value. Both arms must have
    // the SAME type. This replaces the ternary operator from other languages.
    let label = if number > 5 { "big" } else { "small" };
    println!("That number is {}", label);  // -> That number is big

    // Chain conditions with else if.
    let grade = if number >= 9 { "A" } else if number >= 7 { "B" } else { "C" };
    println!("Grade: {}", grade);          // -> Grade: B
}

See how let label = if ... else ... stored a value? That's the expression form. Each arm here yields a &str, so the types line up.

2️⃣ Looping with loop, while, and for

Rust has three loops. loop repeats forever until you break — and uniquely, break can hand a value back out. while repeats while a condition holds. for walks over a range or collection and is the one you'll reach for most.

fn main() {
    // loop runs forever until you break. break can RETURN a value.
    let mut counter = 0;
    let result = loop {
        counter += 1;
        if counter == 5 {
            break counter * 10;   // exit and hand back this value
        }
    };
    println!("loop result: {}", result);   // -> loop result: 50

    // while runs as long as its condition is true.
    let mut n = 3;
    while n > 0 {
        println!("countdown: {}", n);
        n -= 1;
    }

    // for iterates over a range or a collection (the most common loop).
    // 1..=3 is an INCLUSIVE range: 1, 2, 3.
    for i in 1..=3 {
        println!("for i = {}", i);
    }
}

The range 1..=3 is inclusive of 3. Use 1..3 when you want to stop before the last number. The for loop is preferred because it can't run off the end of a collection.

Your turn. Fill in the blanks marked ___, then run it.

// 🎯 YOUR TURN — finish the program, then run it.
fn main() {
    let score = 82;

    // 1) Use if / else if / else to set a letter grade.
    //    90+ = "A", 80+ = "B", otherwise "C".
    let grade = if score >= 90 {
        "A"
    } else if score >= ___ {     // 👉 the threshold for a B
        "B"
    } else {
        "C"
    };
    println!("Grade: {}", grade);     // expected: Grade: B

    // 2) Use a for loop over an INCLUSIVE range 1 to 5 to print each number.
    for i in 1..=___ {           // 👉 the last number to include
        println!("i = {}", i);
    }
}

// ✅ Expected output:
//    Grade: B
//    i = 1
//    i = 2
//    i = 3
//    i = 4
//    i = 5

Mini-Challenge: FizzBuzz

The classic interview warm-up. Combine a for loop with if / else if / else. Run it with cargo run and compare with the full output.

// 🎯 MINI-CHALLENGE: FizzBuzz, the classic.
//
// Loop over the numbers 1 through 15 (inclusive) and for each:
//   - print "FizzBuzz" if it is divisible by BOTH 3 and 5
//   - else print "Fizz" if divisible by 3
//   - else print "Buzz" if divisible by 5
//   - else print the number itself
//
// Hint: use a for loop with 1..=15 and if / else if / else.
// Use n % 3 == 0 to test divisibility by 3.
//
// ✅ First lines of expected output:
//    1
//    2
//    Fizz
//    4
//    Buzz

fn main() {
    // your code here
}

Common Errors (and the fix)

Pro Tips

📋 Quick Reference — Control Flow

GoalRust Syntax
Branchif c { } else { }
if as a valuelet x = if c { 1 } else { 2 };
Infinite looploop { break; }
While loopwhile c { }
For over rangefor i in 0..5 { }
Inclusive range1..=5

🎉 Lesson Complete!

Practice quiz

What type must an if condition evaluate to in Rust?

  • Any non-zero number
  • bool
  • i32
  • Option

Answer: bool. Rust requires a bool; it never treats numbers as truthy, so write a real comparison like n != 0.

Because if is an expression, what can you do with it?

  • Assign its result to a variable
  • Skip the else branch when used as a value
  • Mix different types in its arms
  • Use it only inside loops

Answer: Assign its result to a variable. if is an expression, so let x = if c { a } else { b }; works. Both arms must share a type and the else is required.

Which loop can return a value with break?

  • while
  • for
  • loop
  • None of them

Answer: loop. Only loop can hand a value back out, e.g. break counter * 10; the value becomes the loop expression's result.

What does the range 1..=3 produce?

  • 1, 2
  • 1, 2, 3
  • 0, 1, 2, 3
  • 2, 3

Answer: 1, 2, 3. 1..=3 is inclusive of 3, yielding 1, 2, 3. The = includes the final number.

What does the range 1..3 produce?

  • 1, 2, 3
  • 1, 2
  • 2, 3
  • 0, 1, 2

Answer: 1, 2. 1..3 is exclusive of the end, giving 1 and 2 only. Use 1..=3 to include 3.

Given let r = loop { let mut c = 0; c += 1; if c == 5 { break c * 10; } }; with c counting up from 0, what value does break c * 10 return when c reaches 5?

  • 5
  • 10
  • 50
  • 15

Answer: 50. When c equals 5, break c * 10 returns 5 * 10 = 50.

Which loop is generally preferred for iterating a range or collection?

  • loop
  • while
  • for
  • recursion

Answer: for. for is preferred because it can't run off the end of a collection, avoiding off-by-one and out-of-bounds bugs.

How do you break out of an outer loop from inside a nested inner loop?

  • break all;
  • Label the outer loop and write break 'outer;
  • return;
  • continue outer;

Answer: Label the outer loop and write break 'outer;. Label the loop, e.g. 'outer: loop { ... }, then break 'outer; exits that specific loop.

Why does Rust not need a ternary operator like a ? b : c?

  • It bans conditional values
  • if is already an expression that returns a value
  • match replaces it entirely
  • It uses the && operator instead

Answer: if is already an expression that returns a value. Since if is an expression, let x = if cond { a } else { b }; does exactly what a ternary would.

What error appears if you write if number (an integer) as a condition?

  • expected bool, found integer
  • unused variable
  • overflow
  • non-exhaustive patterns

Answer: expected bool, found integer. Rust won't treat a number as a condition; it expects a bool, so use a comparison like if number != 0.

Continue this course

Frequently asked questions

Why doesn't Rust have a ternary operator like a ? b : c?

It doesn't need one, because if is an expression that already returns a value. You write 'let x = if cond { a } else { b };' which reads clearly and does exactly what a ternary would. The only rule is that both branches must evaluate to the same type, and you must include the else branch when you use the result. This keeps the language smaller while giving you the same capability.

What is the difference between loop, while, and for?

Use loop for an infinite loop you control with break — and uniquely, break can return a value out of a loop. Use while when you want to repeat as long as a condition stays true and you don't know the count in advance. Use for to iterate over a range like 1..=5 or over the items of a collection; it is the most common and the safest, because you can't accidentally run past the end. In idiomatic Rust, for handles the vast majority of looping.

What is the difference between 1..5 and 1..=5?

Both create ranges, but 1..5 is exclusive of the end, giving 1, 2, 3, 4, while 1..=5 is inclusive, giving 1, 2, 3, 4, 5. The '=' includes the final number. A common bug is using the exclusive form when you meant to include the last value, so pick the one that matches what you actually want.

Can I break out of nested loops in Rust?

Yes. You can label a loop with a name like 'outer: loop { ... }' and then write break 'outer; from inside an inner loop to break the outer one specifically. The same works with continue 'outer; to skip to the next iteration of the labelled loop. Loop labels start with a single quote, like a lifetime, and make controlling nested loops explicit and clear.