Pattern Matching and Multiple Clauses
Pattern matching alone can select a clause based on the shape of an argument, such as matching {ok, Value} versus {error, Reason}, but many decisions depend on properties of a value rather than its shape — is this number positive, is this list empty, is this argument actually an integer at all. Guards extend pattern matching with a boolean test attached to a clause head using the when keyword, letting classify(N) when N > 0 -> positive fire only when both the pattern and the condition hold.
Cricket analogy: Pattern matching is like recognizing a bowler is left-arm versus right-arm just by their stance, but a guard is like also checking their economy rate is under 6 before selecting them for the death overs — shape plus condition together decide.
What Are Guards?
A guard is restricted to a small, deliberately limited set of built-in predicates and operators — comparisons like >, <, =:=, type tests like is_integer/1, is_atom/1, is_list/1, and simple arithmetic — precisely because guards must never fail with an exception or perform side effects; if a guard expression would raise an error, Erlang simply treats the guard as false and moves on to the next clause rather than crashing the process. This restriction is what lets the compiler and runtime evaluate guards safely and predictably no matter what garbage gets passed in.
Cricket analogy: It's like a third umpire's review that only checks a fixed set of camera angles and never asks the bowler's opinion — guards only use approved, side-effect-free checks like is_integer/1, never arbitrary calls that might error out.
-module(number_utils).
-export([classify/1, is_valid_age/1]).
classify(N) when N > 0 -> positive;
classify(N) when N < 0 -> negative;
classify(0) -> zero.
is_valid_age(Age) when is_integer(Age), Age >= 0, Age =< 150 ->
true;
is_valid_age(_) ->
false.Guards may use andalso and orelse directly as well as commas and semicolons; is_integer(Age) andalso Age >= 0 inside a single clause behaves identically to writing is_integer(Age), Age >= 0, and reads more clearly to many Erlang programmers once a guard has several parts.
Guard Sequences and Multiple Conditions
Within a single guard, commas act as andalso — every comma-separated condition must be true — while semicolons act as orelse across whole guard sequences, so is_valid(X) when is_integer(X), X > 0; is_valid(X) when X =:= zero -> true reads as '(is_integer(X) and X>0) or (X=:=zero)'. Stacking multiple guarded clauses for the same function name, each with its own when condition, is how Erlang expresses multi-way branching without any if/else syntax at all.
Cricket analogy: It's like a fielding restriction rule stacking two conditions with AND — 'inside the circle AND within the first six overs' — while a semicolon between guard clauses is like an OR rule, such as 'powerplay overs OR final over' triggering a different field setting.
Common Guard Predicates and Pitfalls
A frequent beginner mistake is trying to call an ordinary user-defined function inside a guard — guards only permit a whitelisted set of BIFs, so classify(X) when my_helper(X) -> ... simply won't compile, and the fix is to compute the condition inside the function body instead, often via a case expression. If none of a function's clauses match — no pattern and guard combination succeeds — Erlang raises a function_clause error, which is why it's common practice to end a guard chain with a catch-all clause when full coverage is required.
Cricket analogy: It's like the third umpire being barred from calling the team's own analyst mid-review to ask an opinion — only pre-approved camera checks count, just as guards can't call my_helper(X); and if no clause matches at all, it's like the match ending with no result declared, the equivalent of a function_clause error.
A guard that raises an exception is simply treated as false, not as a crash — so is_valid_age(Age) when Age >= 0, Age =< 150 -> true given an atom instead of a number silently fails the guard and falls through to the next clause rather than halting the program, which can hide bugs if you're not expecting it.
- Guards attach a boolean condition to a function clause using the when keyword.
- Only a restricted set of BIFs and operators are allowed in guards: comparisons, type tests like is_integer/1, and simple arithmetic.
- A guard can never raise an exception; an invalid guard expression simply evaluates to false.
- Commas inside a guard behave like andalso; semicolons between guard sequences behave like orelse.
- Multiple guarded clauses let you express multi-way branching without if/else syntax.
- User-defined functions cannot be called inside a guard expression.
- If no clause's pattern and guard combination matches, Erlang raises a function_clause error.
Practice what you learned
1. What keyword introduces a guard on a function clause?
2. What happens when a guard expression raises an exception internally?
3. In a guard sequence like foo(X) when is_integer(X), X > 0 -> ...; what does the comma mean?
4. Why can't you call a user-defined helper function inside a guard?
5. What error occurs if no clause of a function matches both the pattern and guard for given arguments?
Was this page helpful?
You May Also Like
Functions and Modules
A practical guide to organizing Erlang code into modules and functions, covering declarations, multiple clauses, arity, and exporting a public API.
Recursion in Erlang
Why recursion replaces loops in Erlang, and how to write correct, tail-recursive functions that run in constant stack space.
Lists and Tuples
A comparison of Erlang's two core compound data types — lists and tuples — and when to reach for each.
Related Reading
Related Study Notes in Programming
Browse all study notesApache Spark Study Notes
Programming · 30 topics
ProgrammingApache Flink Study Notes
Programming · 30 topics
ProgrammingHadoop Study Notes
Programming · 30 topics
ProgrammingSnowflake Study Notes
Programming · 30 topics
ProgrammingApache Airflow Study Notes
Programming · 30 topics
Programmingdbt (Data Build Tool) Study Notes
Programming · 30 topics