Dart: Dart Control Flow — Conditional Statements and Loop
Last updated: 2026-08-26
Control flow is the brain of your code — it determines which path your program takes and how many times.
1. What You'll Learn
- if-else / switch-case (including a preview of Dart 3 switch expressions)
- for / while / do-while loops
- for-in and Iterable traversal
- break / continue and labeled control
- Bob's Scenario: DataPipeline data filtering logic
2. A Developer's Real-World Story
(1) Pain Point: Nested if-else Makes Code Unmaintainable
Alice was processing e-commerce order data and wrote a three-level nested if-else structure to filter and classify orders. The logic was: first check the order status, then check the amount range, and finally check the payment method. A 300-line "pyramid" of code required her to understand all the logic for every modification. One missed change led to an error in VIP order discount calculations, resulting in a direct loss of $15,000 USD.
(2) The Solution: Control Flow Best Practices
Using Dart 3's switch expression to replace the nested if-else, and using for-in to replace index-based iteration reduced the code from 300 lines to 80 lines, making the logic clear and maintainable.
// Before: nested if-else pyramid
// After: clean switch expression
String classifyOrder(Order order) => switch ((order.status, order.amount)) {
('pending', > 1000) => 'VIP Pending',
('pending', _) => 'Normal Pending',
('shipped', _) => 'In Transit',
('delivered', _) => 'Completed',
_ => 'Unknown',
};
> **Output:** Run in local DartPad or with `dart run`. All examples in the Dart course are based on Dart 3.x / Flutter 3.x. Results may vary slightly depending on the SDK version.
(3) Benefits
- Switch expressions make branching logic clear at a glance, eliminating nested pyramids.
- for-in traversal is safer, avoiding out-of-bounds errors.
- Labeled control allows precise jump for break/continue in complex loops.
3. if-else Conditional Statements
(1) Basic Syntax
▶ Example
> **Output:** Run in local DartPad or with `dart run`. All examples in the Dart course are based on Dart 3.x / Flutter 3.x. Results may vary slightly depending on the SDK version.
: if-else Basic Usage
void main() {
double orderAmount = 1500.0;
if (orderAmount > 1000) {
print('VIP order - apply discount');
} else if (orderAmount > 500) {
print('Standard order');
} else {
print('Small order');
}
}
> **Output:** Run in local DartPad or with `dart run`. All examples in the Dart course are based on Dart 3.x / Flutter 3.x. Results may vary slightly depending on the SDK version.
▶ Example
> **Output:** Run in local DartPad or with `dart run`. All examples in the Dart course are based on Dart 3.x / Flutter 3.x. Results may vary slightly depending on the SDK version.
: if-else as Expression (Alternative to Ternary Operator)
void main() {
int orderCount = 1500;
// Ternary operator
String tier = orderCount >= 1000 ? 'Enterprise' : 'Standard';
// if-else as expression (Dart 3)
String label = if (orderCount >= 1000) 'Enterprise' else 'Standard';
print('Tier: $tier');
print('Label: $label');
}
> **Output:** Run in local DartPad or with `dart run`. All examples in the Dart course are based on Dart 3.x / Flutter 3.x. Results may vary slightly depending on the SDK version.
| Form | Syntax | Returns Value | Dart Version |
|---|---|---|---|
| if Statement | if (cond) { ... } |
No | All versions |
| Ternary Operator | cond ? a : b |
Yes | All versions |
| if Expression | if (cond) a else b |
Yes | Dart 3.7+ |
4. switch and switch Expressions
(1) Traditional switch Statement
▶ Example
> **Output:** Run in local DartPad or with `dart run`. All examples in the Dart course are based on Dart 3.x / Flutter 3.x. Results may vary slightly depending on the SDK version.
: switch Statement
void main() {
String status = 'shipped';
switch (status) {
case 'pending':
print('Order is waiting for processing');
break;
case 'shipped':
print('Order is in transit');
break;
case 'delivered':
print('Order has been delivered');
break;
default:
print('Unknown status: $status');
}
}
> **Output:** Run in local DartPad or with `dart run`. All examples in the Dart course are based on Dart 3.x / Flutter 3.x. Results may vary slightly depending on the SDK version.
(2) Dart 3 switch Expressions
▶ Example
> **Output:** Run in local DartPad or with `dart run`. All examples in the Dart course are based on Dart 3.x / Flutter 3.x. Results may vary slightly depending on the SDK version.
: switch Expression
// Switch expression - returns a value
String getStatusLabel(String status) => switch (status) {
'pending' => 'Awaiting Processing',
'shipped' => 'In Transit',
'delivered' => 'Completed',
'cancelled' => 'Cancelled',
_ => 'Unknown Status',
};
// Pattern matching with guards
String classifyOrder(double amount) => switch (amount) {
>= 10000 => 'Enterprise',
>= 1000 => 'Premium',
> 0 => 'Standard',
_ => 'Invalid',
};
void main() {
print(getStatusLabel('shipped')); // In Transit
print(classifyOrder(5500)); // Premium
print(classifyOrder(150)); // Standard
}
> **Output:** Run in local DartPad or with `dart run`. All examples in the Dart course are based on Dart 3.x / Flutter 3.x. Results may vary slightly depending on the SDK version.
| Aspect | switch Statement | switch Expression |
|---|---|---|
| Returns Value | No | Yes |
| break | Required | Not needed |
| Syntax | case x: |
x => |
| Dart Version | All | Dart 3+ |
| Exhaustiveness Check | No | Yes (sealed class) |
5. Loop Structures
(1) for Loop
▶ Example
> **Output:** Run in local DartPad or with `dart run`. All examples in the Dart course are based on Dart 3.x / Flutter 3.x. Results may vary slightly depending on the SDK version.
: Traditional for Loop
void main() {
// Classic for loop
for (int i = 0; i < 5; i++) {
print('Processing batch ${i + 1}');
}
// Count down
for (int i = 10; i > 0; i -= 2) {
print('Countdown: $i');
}
}
> **Output:** Run in local DartPad or with `dart run`. All examples in the Dart course are based on Dart 3.x / Flutter 3.x. Results may vary slightly depending on the SDK version.
(2) for-in Loop
▶ Example
> **Output:** Run in local DartPad or with `dart run`. All examples in the Dart course are based on Dart 3.x / Flutter 3.x. Results may vary slightly depending on the SDK version.
: for-in Traversal
void main() {
List``<String>`` sources = ['orders.csv', 'products.csv', 'customers.csv'];
// for-in with List
for (final source in sources) {
print('Loading: $source');
}
// for-in with Set
Set``<String>`` uniqueStatuses = {'pending', 'shipped', 'delivered'};
for (final status in uniqueStatuses) {
print('Status: $status');
}
// for-in with Map entries
Map<String, int> categoryCount = {
'Electronics': 500,
'Clothing': 300,
'Books': 200,
};
for (final entry in categoryCount.entries) {
print('${entry.key}: ${entry.value} products');
}
}
> **Output:** Run in local DartPad or with `dart run`. All examples in the Dart course are based on Dart 3.x / Flutter 3.x. Results may vary slightly depending on the SDK version.
(3) while and do-while Loops
▶ Example
> **Output:** Run in local DartPad or with `dart run`. All examples in the Dart course are based on Dart 3.x / Flutter 3.x. Results may vary slightly depending on the SDK version.
: while and do-while
void main() {
// while - check condition first
int retries = 0;
bool connected = false;
while (!connected && retries < 3) {
print('Connection attempt ${retries + 1}...');
retries++;
connected = retries >= 2; // Simulated success
}
// do-while - execute at least once
int batchSize = 10000;
int processed = 0;
do {
int chunk = (batchSize - processed > 1000) ? 1000 : batchSize - processed;
print('Processing $chunk records...');
processed += chunk;
} while (processed < batchSize);
}
> **Output:** Run in local DartPad or with `dart run`. All examples in the Dart course are based on Dart 3.x / Flutter 3.x. Results may vary slightly depending on the SDK version.
| Loop Type | Initial Check | Minimum Executions | Use Case |
|---|---|---|---|
for |
Before entry | 0 | Known number of times |
for-in |
Before entry | 0 | Traversing collections |
while |
Before entry | 0 | Condition-driven |
do-while |
After execution | 1 | Must execute at least once |
6. break, continue, and Labels
(1) break and continue
▶ Example
> **Output:** Run in local DartPad or with `dart run`. All examples in the Dart course are based on Dart 3.x / Flutter 3.x. Results may vary slightly depending on the SDK version.
: break and continue
void main() {
List``<double>`` amounts = [1500, -50, 3200, 0, 890, -100, 2100];
// continue - skip invalid orders
int validCount = 0;
double totalValid = 0;
for (final amount in amounts) {
if (amount <= 0) continue; // Skip invalid
validCount++;
totalValid += amount;
}
print('Valid: $validCount, Total: $totalValid USD');
// break - stop at first error
for (final amount in amounts) {
if (amount < 0) {
print('Error: Negative amount $amount found!');
break;
}
print('Processing: $amount USD');
}
}
> **Output:** Run in local DartPad or with `dart run`. All examples in the Dart course are based on Dart 3.x / Flutter 3.x. Results may vary slightly depending on the SDK version.
▶ Example
> **Output:** Run in local DartPad or with `dart run`. All examples in the Dart course are based on Dart 3.x / Flutter 3.x. Results may vary slightly depending on the SDK version.
: Labeled Control
void main() {
// Label for nested loop control
outer:
for (int batch = 0; batch < 3; batch++) {
for (int record = 0; record < 5; record++) {
if (record == 2 && batch == 1) {
print('Critical error at batch $batch, record $record');
break outer; // Break out of both loops
}
print('Batch $batch, Record $record');
}
}
}
> **Output:** Run in local DartPad or with `dart run`. All examples in the Dart course are based on Dart 3.x / Flutter 3.x. Results may vary slightly depending on the SDK version.
7. Bob's Scenario: DataPipeline Data Filtering
flowchart TD
A[Raw Data] --> B{Filter Condition}
B -->|Valid Order| C[for-in Iterate]
B -->|Invalid Data| D[continue Skip]
C --> E[Aggregate Calculation]
E --> F[Output Result]
> **Output:** Run in local DartPad or with `dart run`. All examples in the Dart course are based on Dart 3.x / Flutter 3.x. Results may vary slightly depending on the SDK version.
▶ Example
> **Output:** Run in local DartPad or with `dart run`. All examples in the Dart course are based on Dart 3.x / Flutter 3.x. Results may vary slightly depending on the SDK version.
: Order Data Filtering and Aggregation
void main() {
List<Map<String, dynamic>> rawOrders = [
{'id': 'ORD-001', 'amount': 1500.0, 'status': 'completed'},
{'id': 'ORD-002', 'amount': -50.0, 'status': 'completed'},
{'id': 'ORD-003', 'amount': 3200.0, 'status': 'pending'},
{'id': 'ORD-004', 'amount': 0.0, 'status': 'completed'},
{'id': 'ORD-005', 'amount': 890.0, 'status': 'completed'},
{'id': 'ORD-006', 'amount': 2100.0, 'status': 'cancelled'},
];
double totalRevenue = 0;
int completedCount = 0;
Map<String, double> revenueByStatus = {};
for (final order in rawOrders) {
final amount = order['amount'] as double;
final status = order['status'] as String;
// Skip invalid orders
if (amount <= 0) continue;
// Skip cancelled orders
if (status == 'cancelled') continue;
// Aggregate revenue by status
revenueByStatus[status] = (revenueByStatus[status] ?? 0) + amount;
// Count completed
if (status == 'completed') {
completedCount++;
totalRevenue += amount;
}
}
print('=== DataPipeline Filter Report ===');
print('Completed orders: $completedCount');
print('Total revenue: \$${totalRevenue.toStringAsFixed(2)} USD');
for (final entry in revenueByStatus.entries) {
print(' ${entry.key}: \$${entry.value.toStringAsFixed(2)} USD');
}
}
> **Output:** Run in local DartPad or with `dart run`. All examples in the Dart course are based on Dart 3.x / Flutter 3.x. Results may vary slightly depending on the SDK version.
8. Complete Example: DataPipeline Batch Processing Controller
// ============================================
// DataPipeline Batch Processing Controller
// Uses all control flow structures
// ============================================
class BatchProcessor {
final int batchSize;
int processedCount = 0;
int skippedCount = 0;
double totalRevenue = 0;
BatchProcessor({this.batchSize = 1000});
String classifyAmount(double amount) => switch (amount) {
>= 10000 => 'Enterprise',
>= 1000 => 'Premium',
> 0 => 'Standard',
_ => 'Invalid',
};
void processBatch(List<Map<String, dynamic>> orders) {
int batchNum = 0;
for (int i = 0; i < orders.length; i += batchSize) {
batchNum++;
final end = (i + batchSize < orders.length) ? i + batchSize : orders.length;
final batch = orders.sublist(i, end);
print('\n--- Batch $batchNum (${batch.length} records) ---');
for (final order in batch) {
final id = order['id'] as String?;
final amount = order['amount'] as double?;
final status = order['status'] as String?;
// Skip invalid records
if (id == null || amount == null || status == null) {
skippedCount++;
continue;
}
if (amount <= 0) {
skippedCount++;
continue;
}
final tier = classifyAmount(amount);
if (status == 'completed') {
processedCount++;
totalRevenue += amount;
print(' $id: \$${amount.toStringAsFixed(2)} USD [$tier]');
} else if (status == 'pending') {
print(' $id: PENDING - \$${amount.toStringAsFixed(2)} USD [$tier]');
} else {
skippedCount++;
}
}
}
}
void printSummary() {
print('\n=== Processing Summary ===');
print('Processed: $processedCount orders');
print('Skipped: $skippedCount records');
print('Revenue: \$${totalRevenue.toStringAsFixed(2)} USD');
}
}
void main() {
final orders = <Map<String, dynamic>>[
{'id': 'ORD-001', 'amount': 1500.0, 'status': 'completed'},
{'id': 'ORD-002', 'amount': 15500.0, 'status': 'completed'},
{'id': 'ORD-003', 'amount': -50.0, 'status': 'completed'},
{'id': 'ORD-004', 'amount': 890.0, 'status': 'pending'},
{'id': 'ORD-005', 'amount': 3200.0, 'status': 'completed'},
{'id': 'ORD-006', 'amount': 0.0, 'status': 'cancelled'},
];
final processor = BatchProcessor(batchSize: 3);
processor.processBatch(orders);
processor.printSummary();
}
> **Output:** Run in local DartPad or with `dart run`. All examples in the Dart course are based on Dart 3.x / Flutter 3.x. Results may vary slightly depending on the SDK version.
Output:
--- Batch 1 (3 records) ---
ORD-001: $1500.00 USD [Premium]
ORD-002: $15500.00 USD [Enterprise]
--- Batch 2 (3 records) ---
ORD-004: PENDING - $890.00 USD [Standard]
ORD-005: $3200.00 USD [Premium]
=== Processing Summary ===
Processed: 3 orders
Skipped: 3 records
Revenue: $20200.00 USD
❓ FAQ
Q: Is break mandatory in switch statements? A: Yes, in Dart's switch statement, every non-empty case must end with a break, return, throw, or continue. It does not fall through. However, an empty case can fall through.
Q: What is the
_in a switch expression? A:_is a wildcard pattern that matches all remaining cases, equivalent to the default in a switch statement. Dart 3 recommends using_instead of default.
Q: Can for-in modify a collection? A: No. Modifying a collection (adding/removing elements) during a for-in traversal will cause a ConcurrentModificationError. If modification is needed, collect the changes first and apply them afterward.
Q: When should I use while instead of for? A: Use while when the number of iterations is uncertain (e.g., waiting for a network response). Use for/for-in when the number of iterations is known or there is a clear collection to traverse.
Q: Are labels commonly used in actual development? A: Not often. Labeled break/continue is mainly used for precise control in nested loops. In most cases, deep nesting can be avoided through refactoring (extracting methods, using higher-order functions).
Q: Does Dart 3's switch expression support multi-value matching? A: Yes. You can use
|to combine multiple patterns, like'pending' | 'processing' => 'In Progress'.
Q: Is there a performance difference between do-while and while? A: There is no perceptible performance difference. The choice depends on semantics: whether the loop body needs to execute at least once.
📖 Summary
- if-else is the most basic conditional statement. Dart 3 supports if-expression syntax.
- switch expressions (Dart 3) are more concise and safer than switch statements, supporting exhaustiveness checks.
- Four loop types have different use cases: for (known iterations), for-in (traversing collections), while (condition-driven), do-while (at least once).
- break exits a loop, continue skips the current iteration, and labels allow more precise jumps in nested loops.
- DataPipeline uses continue to filter invalid data and switch expressions to classify order tiers.
📝 Exercises
- Basic (Difficulty ⭐): Use for-in to traverse a List containing 5 order amounts. Calculate the total and average, skipping orders with an amount less than or equal to 0.
- Intermediate (Difficulty ⭐⭐): Use a switch expression to implement an order status classifier that returns different processing priority labels based on the combination of (status, amount).
- Challenge (Difficulty ⭐⭐⭐): Implement a paginated batch processor that processes N records per batch, supports break interruption (on encountering a severe error) and continue skipping (for invalid records), and outputs a final processing statistics report.