Control Flow in Erlang
Learn how to direct program flow in Erlang using case, if, guards, multi-clause functions, recursion, and list comprehensions with Docker-ready examples
Control flow is how a program decides what to do next. Most languages reach for if, for, and while to make those decisions. Erlang has an if, but as a multi-paradigm functional language it leans on tools that fit immutable data far better: pattern matching, multi-clause functions, case, guards, and recursion.
The biggest mental shift is that Erlang has no mutable loop counters. Because data is immutable you can never write I = I + 1 to rebind an existing variable, so the traditional counting loop does not exist. Iteration is expressed instead through recursion or through the list comprehensions and lists module functions that wrap it. Branching, meanwhile, is often handled by matching the shape of data rather than testing it with conditionals.
Erlang’s if is also unusual: its branches are guards, not arbitrary boolean expressions, and at least one guard must succeed or the expression raises an error. For that reason case and multi-clause functions do most of the real work. In this tutorial you’ll learn how Erlang handles conditionals (case, if), how guards add conditions to clauses, and how recursion replaces the traditional loop. Every example runs as a standalone escript.
Case: Matching on Shape
case compares a value against a series of patterns and runs the first branch that matches. This is the idiomatic replacement for the switch statement, and it does far more because each pattern can destructure data. Clauses are separated by ;, and the _ pattern is a catch-all.
Create a file named case_match.erl:
| |
Here red, yellow, and green are atoms — constants whose value is their own name, commonly used as labels in Erlang.
If and Guards: Choosing on Conditions
Erlang’s if evaluates a list of guards top to bottom and runs the first one that holds true. Unlike most languages you cannot call arbitrary functions inside a guard — only guard-safe tests like comparisons and rem. The atom true serves as the default branch, guaranteeing the if always has a match.
Create a file named if_guards.erl:
| |
Note =:= — Erlang’s exact equality operator, which checks both value and type. Like case, if is an expression: it returns a value you can bind to a variable.
Guards in Function Clauses
The most idiomatic Erlang branching pushes the decision into the function head. Multiple clauses for the same function are tried in order, and a when guard attaches an extra condition to a clause. The right clause is selected automatically — no conditional inside the body at all.
Create a file named pattern_clauses.erl:
| |
The literal 0 in the first clause matches only the number zero, the guarded clause handles positives, and the _ clause catches everything else.
Recursion: Looping the Functional Way
Erlang has no for or while loop that mutates a counter. Repetition is expressed through recursion. Multi-clause functions make this clean: one clause defines the base case that stops the recursion, another does the work and calls itself with a smaller argument.
Create a file named recursion.erl:
| |
Pattern matching on the literal 0 (and on the empty list []) chooses the stopping clause automatically — no explicit if is needed to decide whether to continue. The [Head | Tail] pattern splits a list into its first element and the rest.
Comprehensions and the Lists Module
In day-to-day code you rarely write raw recursion. List comprehensions and the lists module wrap it for you, giving concise iteration, filtering, and transformation over collections.
Create a file named comprehension.erl:
| |
In the comprehension, X <- lists:seq(1, 10) is a generator and X rem 2 =:= 0 is a filter — only elements passing the filter end up in the result list.
Running with Docker
You can run every example without installing Erlang locally.
| |
Expected Output
Running case_match.erl:
Stop
Go
Unknown signal
Running if_guards.erl:
7 is odd
It is warm (30 degrees)
Running pattern_clauses.erl:
classify(0) = zero
classify(42) = positive
classify(-5) = negative
Running recursion.erl:
3...
2...
1...
Liftoff!
Sum of 1..5 = 15
Running comprehension.erl:
Evens: [2,4,6,8,10]
Line 1
Line 2
Line 3
Squares: [1,4,9,16]
Key Concepts
casematches shape — it compares a value against patterns and can destructure data in the same step, replacing the traditionalswitch; clauses are separated by;.ifevaluates guards — its branches are guard tests, not arbitrary expressions, andtrueprovides the default; at least one branch must succeed or it raises an error.- Guards (
when) select clauses — attaching conditions to function clauses pushes branching into the function head, keeping bodies free of conditionals. - Pattern matching over conditionals — matching literals like
0or the empty list[]often eliminates the need for an explicitifcheck entirely. - Recursion replaces loops — with no mutable counters, repetition is expressed by a function calling itself toward a base case.
[Head | Tail]destructures lists — splitting a list into its first element and remainder is the foundation of most list recursion.- Comprehensions and
lists—[X || X <- List, Condition]and functions likelists:map/2andlists:foreach/2wrap recursion for everyday iteration and filtering. - Everything returns a value —
caseandifare expressions, so their results can be bound directly to a variable.
Comments
Loading comments...
Leave a Comment