Control Flow in Gleam
Learn control flow in Gleam — the case expression that replaces if/else and switch, plus recursion and list functions in place of loops
Control flow decides which code runs and how often. Most imperative languages reach for if/else statements and for/while loops to do this. Gleam, being a small and consistent functional language, takes a more focused approach: there is one branching construct — the case expression — and there are no loops at all.
This surprises newcomers, but it is deliberate. Gleam has no if statement, no switch, no for, and no while. Instead, case does all the branching through pattern matching, and repetition is expressed with recursion or with functions from the standard library’s list module. Because every value is immutable, there is no mutable loop counter to increment — you describe a transformation and let the language carry it out.
A key consequence is that case is an expression: it evaluates to a value you can bind with let or pass to a function. The compiler also checks that your patterns are exhaustive, so if you forget to handle a possible case it tells you at compile time rather than failing at runtime.
In this tutorial you’ll learn how case replaces if/else and switch, how guards add conditions to patterns, how to destructure tuples and lists while matching, and how recursion and list functions take the place of traditional loops.
Conditionals with case
Since Gleam has no if statement, two-way and multi-way branching are both written with case. Guards — introduced with the if keyword inside a pattern — let a branch match only when an extra condition holds. The wildcard _ is the catch-all.
Create a file named conditionals.gleam:
| |
Patterns are tried top to bottom, so order matters: temperature is 18, which fails < 0 and < 15 but matches < 25, giving “comfortable”. Because case returns a value, the sign and status bindings capture the result of the branch that matched — there is no separate ternary operator because case already fills that role.
Pattern Matching and Destructuring
The real power of case shows when you match the shape of data. Alternative patterns with | let one branch cover several values, and you can destructure tuples and lists directly in the pattern, binding their parts to names.
Create a file named pattern_matching.gleam:
| |
The tuple #(0, 5) matches #(0, _) because its first element is 0 and the second is anything, so the result is “on the y-axis”. The list [1, 2, 3] is neither empty nor a single item, so it matches [first, ..rest], binding first to 1 and rest to [2, 3]. This head-and-tail destructuring is the foundation of list processing in Gleam.
Recursion: Repetition Without Loops
Gleam has no for or while loops. Repetition is expressed with recursion — a function that calls itself, using case to decide when to stop. Pairing recursion with an accumulator argument gives tail recursion, which the BEAM runs in constant stack space.
Create a file named recursion.gleam:
| |
Each function uses case to test for its base case (0) and otherwise recurses on a smaller value. The sum_to function carries the running total in its acc parameter and only calls itself in tail position, so it never grows the call stack — the idiomatic way to loop a fixed number of times in Gleam.
Iterating Over Collections
For working with lists, you rarely write the recursion by hand. The standard library’s list module provides higher-order functions that express the common patterns — running a side effect, transforming, filtering, and reducing — without an explicit loop.
Create a file named list_iteration.gleam:
| |
These functions are where a Gleam programmer’s mind goes first: instead of “loop over the list and accumulate a sum,” you write list.fold. Because the list is immutable, list.map and list.filter return brand-new lists and leave the original numbers untouched. The string.inspect function renders any value as a readable string, which is handy for printing lists.
Running with Docker
Gleam needs a project structure, so each Docker command creates one on the fly and copies your source file into it as hello.gleam before running.
| |
Expected Output
Running conditionals.gleam produces:
18 degrees is comfortable
-7 is negative
Age 20: adult
Running pattern_matching.gleam produces:
Sat is a weekend
on the y-axis
starts with 1, 2 more
Running recursion.gleam produces:
Countdown:
5
4
3
2
1
Liftoff!
5! = 120
Sum 1..100 = 5050
Running list_iteration.gleam produces:
Each number:
1
2
3
4
5
Doubled: [2, 4, 6, 8, 10]
Evens: [2, 4]
Sum: 15
Key Concepts
- No
ifstatement — Gleam has noif,switch,for, orwhile. Thecaseexpression is the single branching construct, and recursion orlistfunctions handle repetition. caseis an expression — It evaluates to a value you can bind withletor pass along, which is why Gleam needs no separate ternary operator.- Guards refine patterns — Writing
ifafter a pattern (t if t < 15 ->) lets a branch match only when an extra condition holds; patterns are tried top to bottom. - Exhaustiveness checking — The compiler rejects a
casethat doesn’t cover every possibility, catching missed cases before the program runs. - Destructuring while matching — Patterns can pull apart tuples (
#(x, y)) and lists ([first, ..rest]), and|lets one branch match several alternatives. - Recursion replaces loops — A function calls itself with a smaller value and uses
casefor its base case; an accumulator argument gives tail recursion that runs in constant stack space. - List functions over manual loops —
list.each,list.map,list.filter, andlist.foldexpress iteration declaratively and return new lists, since all data is immutable. - No truthy values —
caseon aBoolmatchesTrue/Falseexplicitly; Gleam never treats numbers, strings, or lists as conditions.
Running Today
All examples can be run using Docker:
docker pull ghcr.io/gleam-lang/gleam:v1.14.0-erlang-alpine
Comments
Loading comments...
Leave a Comment