100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
Programming

Guards and case in Elixir

Learn how Elixir's case expression and guard clauses let you branch on pattern matches with additional boolean conditions, and when to reach for cond instead.

Core ConceptsIntermediate8 min readJul 10, 2026
Analogies

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.

elixir
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

Was this page helpful?

Topics covered

#Programming#ElixirStudyNotes#GuardsAndCaseInElixir#Guards#Case#Elixir#Expression#StudyNotes#SkillVeris#ExamPrep