Chapter 3: Control Flow
Making Decisions
Every interesting program makes decisions. Should we process this file? Is the user authenticated? Did the network request succeed?
The if statement is how you express these decisions in code:
fn main() -> int {
var temperature = 25
if (temperature > 30) {
print("It's hot")
}
return 0
}If the condition is true, the code in the braces runs. If it's false, it doesn't. Simple.
Notice the parentheses around the condition-they're required in Wyn. This is deliberate. It makes the code unambiguous and easier to parse, both for the compiler and for humans.
The else Branch
Often you want to do one thing if a condition is true, and something else if it's false:
fn main() -> int {
var age = 18
if (age >= 18) {
print("Can vote")
} else {
print("Too young")
}
return 0
}Chaining Conditions
When you have multiple conditions to check, chain them with else if:
fn main() -> int {
var score = 85
if (score >= 90) {
print("A")
} else if (score >= 80) {
print("B")
} else if (score >= 70) {
print("C")
} else if (score >= 60) {
print("D")
} else {
print("F")
}
return 0
}The conditions are checked in order. As soon as one is true, that branch runs and the rest are skipped. This matters:
var score = 95
if (score >= 60) {
print("D") // This runs first!
} else if (score >= 90) {
print("A") // Never reached
}Order your conditions from most specific to least specific.
Loops: Doing Things Repeatedly
The while Loop
A while loop runs as long as its condition is true:
fn main() -> int {
var count = 0
while (count < 5) {
print(count)
count = count + 1
}
return 0
}This prints 0, 1, 2, 3, 4. The condition is checked before each iteration. When count reaches 5, the condition becomes false and the loop stops.
Be careful with while loops. If the condition never becomes false, you have an infinite loop:
var count = 0
while (count < 5) {
print(count)
// Forgot to increment count!
}This will run forever. Well, until you kill the process.
The for Loop
When you know how many times you want to loop, use for:
fn main() -> int {
for (var i = 0; i < 5; i = i + 1) {
print(i)
}
return 0
}A for loop has three parts:
- Initialization:
var i = 0- runs once before the loop starts - Condition:
i < 5- checked before each iteration - Update:
i = i + 1- runs after each iteration
This is equivalent to:
var i = 0
while (i < 5) {
print(i)
i = i + 1
}But the for loop is more compact and keeps the loop control in one place.
Iterating Over Arrays
The idiomatic way to loop through an array is for-in:
fn main() -> int {
var numbers = [10, 20, 30, 40, 50]
for n in numbers {
println(n.to_string())
}
return 0
}Need the index too? Use for i, v in:
fn main() -> int {
var names = ["Alice", "Bob", "Charlie"]
for i, name in names {
println(i.to_string() + ": " + name)
}
// 0: Alice
// 1: Bob
// 2: Charlie
return 0
}For ranges, use for i in start..end:
for i in 0..5 {
println(i.to_string()) // 0, 1, 2, 3, 4
}You can also use the C-style for loop when you need more control:
fn main() -> int {
var numbers = [10, 20, 30, 40, 50]
for (var i = 0; i < numbers.len(); i = i + 1) {
println(numbers[i].to_string())
}
return 0
}Breaking Out: Control Statements
break: Exit Early
Sometimes you want to exit a loop before it naturally finishes:
fn main() -> int {
var numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
for (var i = 0; i < numbers.len(); i = i + 1) {
if (numbers[i] == 5) {
print("Found 5!")
break
}
print(numbers[i])
}
return 0
}Output:
1
2
3
4
Found 5!When break executes, the loop stops immediately. Control jumps to the first statement after the loop.
continue: Skip to Next Iteration
continue skips the rest of the current iteration and jumps to the next one:
fn main() -> int {
for (var i = 0; i < 10; i = i + 1) {
if (i % 2 == 0) {
continue // Skip even numbers
}
print(i)
}
return 0
}Output:
1
3
5
7
9Use continue when you want to skip certain cases without nesting more if statements.
return: Exit the Function
return exits the entire function, not just the loop:
fn find_first_negative(numbers: [int]) -> int {
for (var i = 0; i < numbers.len(); i = i + 1) {
if (numbers[i] < 0) {
return numbers[i] // Exit function immediately
}
}
return 0 // No negative found
}
fn main() -> int {
var nums = [5, 3, -2, 8, -1]
var result = find_first_negative(nums)
print(result) // -2
return 0
}Patterns You'll Use Constantly
Searching
Finding something in a collection:
fn main() -> int {
var names = ["Alice", "Bob", "Charlie", "Diana"]
var target = "Charlie"
var found = false
for (var i = 0; i < names.len(); i = i + 1) {
if (names[i] == target) {
found = true
break
}
}
if (found) {
print("Found!")
} else {
print("Not found")
}
return 0
}This is the linear search algorithm. It's O(n)-you might check every element. For small arrays, that's fine. For large ones, you want a better data structure (we'll get to that).
Accumulation
Building up a result:
fn main() -> int {
var numbers = [1, 2, 3, 4, 5]
var sum = 0
for (var i = 0; i < numbers.len(); i = i + 1) {
sum = sum + numbers[i]
}
print(sum) // 15
return 0
}Start with an initial value (often 0 or an empty collection), then update it in each iteration. This pattern is everywhere: summing, counting, building strings, constructing arrays.
Finding Maximum
fn main() -> int {
var numbers = [3, 7, 2, 9, 1, 5]
var max = numbers[0] // Assume first is max
for (var i = 1; i < numbers.len(); i = i + 1) {
if (numbers[i] > max) {
max = numbers[i]
}
}
print(max) // 9
return 0
}Note we start at index 1, not 0, since we already used the first element as our initial max.
Filtering
Selecting elements that match a condition:
fn main() -> int {
var numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
var evens = []
for (var i = 0; i < numbers.len(); i = i + 1) {
if (numbers[i] % 2 == 0) {
evens.push(numbers[i])
}
}
// evens is now [2, 4, 6, 8, 10]
return 0
}Nested Loops: When One Isn't Enough
Sometimes you need loops inside loops:
fn main() -> int {
// Print a multiplication table
for (var i = 1; i <= 5; i = i + 1) {
for (var j = 1; j <= 5; j = j + 1) {
print(i * j)
print(" ")
}
print("\n")
}
return 0
}The inner loop runs completely for each iteration of the outer loop. If the outer loop runs 5 times and the inner loop runs 5 times, you get 25 total iterations.
Be careful with nested loops-they multiply complexity. Two nested loops over arrays of size n give you O(n²) time complexity. That's fine for small n, but it gets slow fast.
Guard Clauses: Early Returns
Instead of nesting conditions, return early:
fn process_user(age: int, name: string) -> int {
// Bad: nested conditions
if (age >= 18) {
if (name != "") {
if (name.len() < 50) {
// Actually do something
print("Processing user")
}
}
}
return 0
}This is hard to read. Use guard clauses instead:
fn process_user(age: int, name: string) -> int {
// Good: guard clauses
if (age < 18) {
return 0
}
if (name == "") {
return 0
}
if (name.len() >= 50) {
return 0
}
// Actually do something
print("Processing user")
return 0
}Check for invalid conditions first and return early. The main logic stays at the top level, not buried in nested blocks.
What You've Learned
Control flow is about making decisions and repeating actions. if statements let you branch based on conditions. Loops let you repeat code. break, continue, and return let you exit early.
The patterns-searching, accumulating, filtering-are fundamental. You'll use them in almost every program you write.
Guard clauses keep code flat and readable. Nested loops multiply complexity, so use them carefully.
These are the building blocks. Everything else is just combinations of these primitives.
Exercises
FizzBuzz: Print numbers 1-100. For multiples of 3, print "Fizz". For multiples of 5, print "Buzz". For multiples of both, print "FizzBuzz". This is a classic interview question-make sure you can do it.
Prime checker: Write a function that returns true if a number is prime, false otherwise. A number is prime if it's only divisible by 1 and itself.
Reverse an array: Given an array, print its elements in reverse order. Don't create a new array-just iterate backwards.
Find duplicates: Given an array, print any values that appear more than once. This is harder than it looks.
Nested loops: Print a triangle pattern:
* ** *** **** *****Early exit: Write a function that searches for a value in an array and returns its index. If not found, return -1. Use
returnto exit as soon as you find it.Guard clauses: Take a function with nested
ifstatements and rewrite it using guard clauses. Notice how much more readable it becomes.
Next: Chapter 4: Functions
Try It Yourself
Install Wyn and run this example:
curl -sSL wynlang.com/install.sh | sh
wyn init hello && cd hello
wyn run src/main.wyn