Beginner

Control Flow in Odin

Learn conditionals, switch statements, and loops in Odin -- if/else, switch with ranges, the unified for loop, and loop control with Docker-ready examples

Control flow determines the order in which statements execute – which branches run, how often code repeats, and when a loop stops. As an imperative, procedural language, Odin handles control flow with familiar structured constructs, but it sands off many of C’s rough edges in the process.

Two design choices stand out. First, Odin has a single loop keyword – for – that covers every kind of iteration, from C-style counting to range iteration to infinite loops. There is no separate while keyword; a for with just a condition is the while loop. Second, Odin’s switch does not fall through between cases by default, eliminating a classic source of C bugs. You opt into fall-through explicitly with fallthrough.

In keeping with Odin’s “explicit over implicit” philosophy, conditions are not wrapped in parentheses, blocks always require curly braces, and there is no hidden control flow. This tutorial walks through conditionals, switch statements, the unified for loop, and loop control statements like break and continue.

Conditionals: if, else, and the ternary

Odin’s if statement needs no parentheses around the condition, but the body always uses braces. A handy feature is the initialization statement: you can declare a variable scoped only to the if/else chain.

Create a file named conditionals.odin:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package main

import "core:fmt"

main :: proc() {
    temperature := 18

    // Basic if / else if / else -- no parentheses around the condition
    if temperature > 25 {
        fmt.println("It's warm")
    } else if temperature > 10 {
        fmt.println("It's mild")
    } else {
        fmt.println("It's cold")
    }

    // If with an initialization statement.
    // `score` is scoped to this if/else chain only.
    if score := temperature * 5; score >= 90 {
        fmt.println("High score:", score)
    } else {
        fmt.println("Score is:", score)
    }

    // Ternary expression for simple value selection
    label := temperature > 15 ? "above" : "below"
    fmt.println("Temperature is", label, "15")
}

The initialization statement (score := temperature * 5;) runs once before the condition is tested, and score is only visible inside the if/else. This keeps short-lived helper variables out of the surrounding scope. The ternary operator cond ? a : b returns one of two values inline.

Switch statements

Odin’s switch is more capable than C’s. Cases do not fall through automatically, a single case can match a list of values, and cases can match ranges. The default case is written simply as case: with no value.

Create a file named switch.odin:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
package main

import "core:fmt"

main :: proc() {
    day := 3

    // Switch on a value -- no automatic fallthrough, no `break` needed
    switch day {
    case 1:
        fmt.println("Monday")
    case 2, 3, 4:
        // A single case can match multiple values
        fmt.println("Midweek")
    case 5:
        fmt.println("Friday")
    case:
        // The default case has no value
        fmt.println("Weekend")
    }

    // Switch with ranges: ..= is inclusive, ..< is half-open
    grade := 84
    switch grade {
    case 90..=100:
        fmt.println("Grade: A")
    case 80..<90:
        fmt.println("Grade: B")
    case 70..<80:
        fmt.println("Grade: C")
    case:
        fmt.println("Grade: F")
    }

    // A bare `switch` (switch true) replaces long if/else chains
    n := -7
    switch {
    case n < 0:
        fmt.println("negative")
    case n == 0:
        fmt.println("zero")
    case:
        fmt.println("positive")
    }
}

The third form – switch with no expression – evaluates each case as a boolean condition, top to bottom. It is Odin’s idiomatic replacement for a long if/else if chain and is often clearer.

The unified for loop

Odin has exactly one loop keyword: for. It takes several shapes depending on how many clauses you give it.

Create a file named loops.odin:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package main

import "core:fmt"

main :: proc() {
    // C-style for loop: init; condition; post
    fmt.println("Counting up:")
    for i := 0; i < 3; i += 1 {
        fmt.println(i)
    }

    // While-style: a for loop with only a condition
    fmt.println("Countdown:")
    n := 3
    for n > 0 {
        fmt.println(n)
        n -= 1
    }

    // Range-based loop over a half-open interval (0, 1, 2)
    fmt.println("Range 0..<3:")
    for i in 0..<3 {
        fmt.println(i)
    }

    // Inclusive range (1, 2, 3)
    fmt.println("Range 1..=3:")
    for i in 1..=3 {
        fmt.println(i)
    }
}

A for with three clauses is the classic counting loop. Drop the init and post clauses and keep only the condition, and you have a while loop. The for x in start..<end form iterates over a numeric range without manual index bookkeeping. (An empty for { } with no clauses at all is an infinite loop.)

Loop control: break, continue, and labels

break exits a loop early and continue skips to the next iteration. Odin also supports labeled loops, letting you break out of an outer loop from within a nested one. Range loops can yield both the value and its index when iterating over collections.

Create a file named loop_control.odin:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
package main

import "core:fmt"

main :: proc() {
    numbers := [5]int{10, 20, 30, 40, 50}

    // Iterate over a fixed array: value and index
    for value, index in numbers {
        fmt.printf("numbers[%d] = %d\n", index, value)
    }

    // continue skips the rest of the current iteration
    fmt.println("Odd numbers only:")
    for i in 1..=10 {
        if i % 2 == 0 {
            continue
        }
        fmt.println(i)
    }

    // break exits the loop early
    fmt.println("Stop at 5:")
    for i in 1..=100 {
        if i > 5 {
            break
        }
        fmt.println(i)
    }

    // Labeled break to exit nested loops at once
    fmt.println("Nested with label:")
    outer: for x in 0..<3 {
        for y in 0..<3 {
            if x + y == 3 {
                break outer
            }
            fmt.printf("(%d, %d)\n", x, y)
        }
    }
}

When iterating with for value, index in collection, Odin gives you both the element and its position – no separate counter to manage. The label outer: attached to the outer loop lets break outer terminate both loops in one statement, which would otherwise require a flag variable in C.

Running with Docker

Each example is a standalone package main program, so run them one at a time. The file is copied to /tmp (where the compiler has write access for the output binary), and the -file flag tells Odin to compile that single file rather than the whole directory.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# Pull the official Odin image
docker pull primeimages/odin:latest

# Run the conditionals example
docker run --rm -v $(pwd):/app -w /app primeimages/odin:latest sh -c 'cp conditionals.odin /tmp/ && cd /tmp && odin run conditionals.odin -file'

# Run the switch example
docker run --rm -v $(pwd):/app -w /app primeimages/odin:latest sh -c 'cp switch.odin /tmp/ && cd /tmp && odin run switch.odin -file'

# Run the loops example
docker run --rm -v $(pwd):/app -w /app primeimages/odin:latest sh -c 'cp loops.odin /tmp/ && cd /tmp && odin run loops.odin -file'

# Run the loop control example
docker run --rm -v $(pwd):/app -w /app primeimages/odin:latest sh -c 'cp loop_control.odin /tmp/ && cd /tmp && odin run loop_control.odin -file'

Expected Output

Running conditionals.odin:

It's mild
High score: 90
Temperature is above 15

Running switch.odin:

Midweek
Grade: B
negative

Running loops.odin:

Counting up:
0
1
2
Countdown:
3
2
1
Range 0..<3:
0
1
2
Range 1..=3:
1
2
3

Running loop_control.odin:

numbers[0] = 10
numbers[1] = 20
numbers[2] = 30
numbers[3] = 40
numbers[4] = 50
Odd numbers only:
1
3
5
7
9
Stop at 5:
1
2
3
4
5
Nested with label:
(0, 0)
(0, 1)
(0, 2)
(1, 0)
(1, 1)

Key Concepts

  • No parentheses around conditions - if x > 0 { } is the Odin style; braces are always required around the body.
  • Initialization statements - if init; condition { } scopes a helper variable to just the if/else chain, keeping the surrounding scope clean.
  • One loop keyword - for covers C-style loops, while loops (condition only), range loops (for i in 0..<n), and infinite loops (for { }). There is no separate while.
  • Range operators - ..< is half-open (excludes the end) and ..= is inclusive (includes the end); both work in for ranges and switch cases.
  • Switch never falls through - Cases break automatically, a single case can list multiple values, and a bare switch evaluates boolean cases like an if/else chain. Use fallthrough only when you explicitly want C-style behavior.
  • Value-and-index iteration - for value, index in collection hands you both without a manual counter.
  • Labeled loops - Attach a label like outer: to a loop so break outer or continue outer can target it from inside nested loops.
  • Ternary expressions - cond ? a : b selects between two values inline for simple cases.

Running Today

All examples can be run using Docker:

docker pull primeimages/odin:latest
Last updated:

Comments

Loading comments...

Leave a Comment

2000 characters remaining