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
- Branching with if, else if, and else
- Using if as an expression that returns a value
- The infinite loop and breaking with a value
- Repeating with while until a condition is false
- Iterating with for over ranges and collections
- The difference between exclusive and inclusive ranges
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 = 5Mini-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)
- "expected `bool`, found integer" — Rust won't treat a number as a condition. Write a real comparison, e.g. if n != 0 instead of if n.
- "`if` and `else` have incompatible types" — When using if as a value, both arms must return the same type, and you need an else.
- Off-by-one with ranges — 1..5 stops at 4. Use 1..=5 to include 5.
- Infinite while loop — Forgetting to change the condition variable (like n -= 1) means the loop never ends. Update it inside the loop.
Pro Tips
- 💡 Prefer for over while: iterating a range or collection with for avoids off-by-one and out-of-bounds bugs.
- 💡 Return values from loop: break value; inside a loop is a clean way to compute then exit.
- 💡 Label nested loops: use 'outer: loop and break 'outer; to control which loop you exit.
📋 Quick Reference — Control Flow
| Goal | Rust Syntax |
|---|---|
| Branch | if c { } else { } |
| if as a value | let x = if c { 1 } else { 2 }; |
| Infinite loop | loop { break; } |
| While loop | while c { } |
| For over range | for i in 0..5 { } |
| Inclusive range | 1..=5 |
🎉 Lesson Complete!
- ✅ if conditions must be bool; if is an expression that can return a value
- ✅ loop repeats forever and break value returns a value
- ✅ while repeats while a condition is true
- ✅ for iterates ranges and collections; 1..=5 is inclusive
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
- Previous: Functions
- Next: Ownership — Rust's signature feature — how memory is managed without a garbage collector
- Quick reference: Rust cheat sheet
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.