The case Expression
case value do pattern -> result end tries each clause's pattern against value in the order they're written, and executes the first branch that matches. If none of the patterns match and there's no catch-all _ -> clause, Elixir raises a CaseClauseError at runtime rather than silently doing nothing, which makes case expressions a strict, exhaustive-by-convention way to branch on a value's shape.
Cricket analogy: case works like the DRS protocol checking evidence in a fixed order -- ball-tracking first, then edge detection, then umpire's call -- and if no rule covers a bizarre situation, the third umpire has no ruling to give, just as an unmatched case without _ raises a CaseClauseError.
Adding Guards with when
A guard clause, introduced with when, attaches an extra boolean condition on top of a pattern match, as in x when x > 0 -> :positive. Guards are restricted to a limited set of fast, side-effect-free expressions -- comparisons, arithmetic, and type checks like is_integer/1 or is_binary/1 -- because the BEAM must be able to evaluate them quickly and safely while selecting a clause; arbitrary custom function calls are not permitted inside a guard.
Cricket analogy: A guard like x when x > 0 is like a run-out appeal valid only 'when the bails are off AND the batsman is out of the crease' -- a fast check the umpire confirms from footage, not a full biomechanical analysis, which is why guards allow only quick built-ins.
Guards allow and, or, and not, but NOT the short-circuit operators &&, ||, and !. Unlike &&/||, which accept any truthy or falsy value, and/or in a guard strictly require a boolean left operand -- x > 0 and is_integer(x) is valid, but using && in that position raises a compile error because it isn't in the restricted guard-safe list.
def classify(n) do
case n do
x when x > 0 -> :positive
x when x < 0 -> :negative
_ -> :zero
end
end
classify(5) # :positive
classify(-3) # :negative
classify(0) # :zero
def area(:circle, r) when r > 0, do: :math.pi() * r * r
def area(:square, s) when s > 0, do: s * s
def area(_shape, _size), do: {:error, :invalid_shape}
def grade(score) do
cond do
score >= 90 -> "A"
score >= 80 -> "B"
score >= 70 -> "C"
true -> "F"
end
end
Guards in Function Heads
Guards work identically at the function-head level: def area(:circle, r) when r > 0, do: ... defines one clause among several, each handling a different guarded pattern for the same function name and arity. This lets you dispatch on both the shape of the arguments and an extra condition without writing any conditional logic inside the function body at all.
Cricket analogy: Defining field_position(:pace, over) and field_position(:spin, over) as separate clauses is like a captain's fielding manual having distinct chapters for pace-bowling overs and spin-bowling overs, selected automatically by which bowler is running in, rather than one giant chapter full of if-checks.
cond as an Alternative
cond evaluates a series of independent boolean expressions in order and executes the first one that's true, without matching against a specific value's structure at all -- useful when the branches are unrelated conditions rather than variations of one pattern. A cond with no matching branch and no final true -> clause raises a CondClauseError, so idiomatic cond blocks almost always end with true -> default_result.
Cricket analogy: cond is like a national selection committee weighing unrelated factors -- is he in top form? do we need a left-arm option? is he match-fit? -- none tied to one delivery's shape, unlike case which matches a specific ball type or a guard narrowing an already-matched condition.
Choose case when branching on the shape of a single value, guards when you need to refine a matched pattern with a fast boolean check, and cond when evaluating a chain of unrelated true/false conditions. A cond block should end with true -> ... as its catch-all, the same way a case block should end with _ -> ....
- case value do pattern -> result end tries each clause's pattern top to bottom until one matches.
- An unmatched value in a case expression raises CaseClauseError unless a catch-all _ clause is present.
- Guard clauses use the when keyword to add a boolean condition on top of a pattern match.
- Guards allow and/or/not with strictly boolean operands, but not the short-circuit &&/||/! operators.
- Guards can appear both in case clauses and directly in function head definitions.
- cond evaluates a series of independent boolean conditions and runs the first one that's true, ending in true -> as a catch-all.
- Choose case for pattern-based branching, cond for unrelated boolean conditions, and guards to refine either.
Practice what you learned
1. What happens if a case expression has no clause matching the given value and no catch-all _ clause?
2. In `case n do x when x > 0 -> :positive; _ -> :other end`, what role does `when x > 0` play?
3. Which operators are NOT allowed inside a guard clause?
4. What distinguishes cond from case?
5. Can guards be used directly in function head definitions, not just inside case?
Was this page helpful?
You May Also Like
Recursion and Enum
Explore how Elixir uses recursion for iteration and how the Enum and Stream modules provide higher-level functions built on that foundation.
Functions and Modules in Elixir
Learn how Elixir organizes code into modules and functions, covering named functions, arity, default arguments, private functions, module attributes, and anonymous functions.
Structs in Elixir
Learn how Elixir structs extend maps with defined fields, compile-time checks, default values, and protocol-based behavior.
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