Control Flow: Decisions and Loops
So far, your Dart programs have mostly run from top to bottom, one line after another. Real programs need to make decisions ("if the cart is empty, show a message") and repeat work ("for each item in the list, print it"). That is control flow: the ability to choose which lines of your program run, in what order, and how many times. In this chapter you will learn every branching and looping tool Dart gives you, and a few habits that keep them readable.
Deciding with if, else if, and else
The if statement runs a block of code only when a condition is true. A condition is any expression that evaluates to true or false. Dart is strict here: the condition must be a real boolean, not a number or a string. There is no "truthy" shortcut like in JavaScript or Python.
int temperature = 30;
if (temperature > 28) {
print("It's hot.");
} else if (temperature > 15) {
print("It's mild.");
} else {
print("It's cold.");
}Dart checks each condition from top to bottom and runs only the first block whose condition is true. If none match, the else block runs. You can chain as many else if branches as you need, but if you find yourself chaining five or more, a switch is usually clearer.
Combining conditions
Conditions can be combined with logical operators:
| Operator | Name | True when | ||
|---|---|---|---|---|
| `&&` | and | both sides are true | ||
| ` | ` | or | at least one side is true | |
| `!` | not | the value is false |
int age = 20;
bool hasTicket = true;
if (age >= 18 && hasTicket) {
print("You may enter.");
}A handy detail: && and || short-circuit. If the left side of && is false, Dart never checks the right side. That makes patterns like if (name != null && name.isNotEmpty) safe, because the second check only runs when name is not null.
Loops: repeating work
The for loop
The most common loop runs a fixed number of times or walks through a collection.
for (var i = 1; i <= 3; i++) {
print("Lap $i");
}
var fruits = ['apple', 'banana', 'cherry'];
for (var fruit in fruits) {
print(fruit);
}The first form has three parts: a starting statement (var i = 1), a condition checked before each round (i <= 3), and a step run after each round (i++ adds one to i). The second form, the for-in loop, hands you each element one at a time. Prefer for-in whenever you are walking a list, set, or map, because it is shorter and has no index variable to get wrong.
while and do-while
Use while when you do not know in advance how many rounds you need.
var cups = 0;
while (cups < 3) {
cups++;
print("Filled cup $cups");
}The condition is checked before each round, so a while loop may run zero times. The do-while loop checks after, so it always runs at least once — useful for "ask the user, then decide whether to ask again" situations.
var attempts = 0;
do {
attempts++;
print("Attempt $attempts");
} while (attempts < 2);break and continue
Two keywords fine-tune a loop. break leaves the loop immediately. continue skips the rest of the current round and moves to the next one.
for (var i = 0; i < 10; i++) {
if (i == 3) continue; // skip 3
if (i == 6) break; // stop entirely
print(i);
}This prints 0, 1, 2, 4, 5. Keep break and continue rare; a loop full of them is hard to follow, and a well-chosen condition often does the same job more clearly.
Choosing between many cases with switch
A switch statement compares one value against several possibilities. In Dart, each case body does not fall through to the next one — when a case finishes, control leaves the switch. No stray break needed, unlike C or Java.
String command = 'help';
switch (command) {
case 'help':
print("Available commands: help, start, stop");
break; // optional, but conventional
case 'start':
case 'go':
print("Starting!");
break;
default:
print("Unknown command: $command");
}Note that 'start' and 'go' share a body: empty cases simply flow into the next case with code. The default case catches anything not listed.
Modern Dart also has switch expressions, which produce a value instead of running statements:
String describe(int n) => switch (n) {
0 => 'zero',
1 || 2 || 3 => 'small',
_ => 'many',
};The => means "this case's result is." The underscore _ is a catch-all, playing the role of default. Use a switch expression when every branch just returns a value; use a switch statement when branches do several lines of work.
Putting it together
Here is a small program that combines decisions and loops to tally scores:
void main() {
var scores = [7, 3, 9, 3, 10];
var total = 0;
for (var score in scores) {
if (score == 10) {
print("Perfect!");
} else if (score < 5) {
continue; // low scores don't count
}
total += score;
}
print("Total: $total");
var grade = switch (total) {
>= 30 => 'gold',
>= 20 => 'silver',
_ => 'bronze',
};
print("Grade: $grade");
}The loop walks the list, the branches decide what to do with each score, continue filters out low scores, and a switch expression turns the total into a label. This pattern — loop, branch, accumulate — appears in almost every program you will write.
Show diagram source
flowchart TD
A[Loop condition] -->|true| B[Loop body]
A -->|false| E[After loop]
B --> C{score == 10?}
C -->|yes| D[Print Perfect]
C -->|no| F[Add to total]
D --> F
F --> AExercise
What does this loop print? for (var i = 0; i < 5; i++) { if (i % 2 == 0) continue; print(i); }
Show answer
It prints 1 and 3. Even numbers trigger continue, skipping the print, so only the odd values in range are printed.
Exercise
You need to keep asking a user for input until they type "quit". Which loop fits best, and why?
Show answer
A while loop fits best: you don't know how many rounds you need, and the loop should stop before running if the first input is already "quit". A do-while would run its body at least once regardless.
Exercise
Rewrite this if-else chain as a switch expression: if n is 1 return 'one'; if n is 2 return 'two'; otherwise return 'other'.
Show answer
var word = switch (n) { 1 => 'one', 2 => 'two', _ => 'other' };
Spotted a mistake, or code that does not work?