Pattern Matching with Multiple Function Clauses

Welcome. In this course on advanced pattern matching in Elixir, we start by using multiple function clauses to route different inputs to the correct logic. If you are new to this idea, think of it as matching the “shape” of data right in the function head. This builds on core Elixir fundamentals and prepares you for later topics like guards, case statements, and the pin operator.

Walkthrough: A Calculator Using Function Clauses

Explanation:

  • defmodule Calculator do … end defines a module named Calculator.
  • Multiple def calculate clauses each match a different tuple pattern:
    • {:add, a, b} returns a + b
    • {:subtract, a, b} returns a - b
    • {:multiply, a, b} returns a * b
  • Division is handled with two clauses:
    • {:divide, a, b} when b != 0 uses a guard to ensure b is not zero, then returns a / b.
    • {:divide, _a, 0} matches divide-by-zero and returns an error tuple. The underscore in _a means “ignore this value.”
  • Order matters. Elixir picks the first clause whose pattern matches and whose guard (if any) evaluates to true.
  • The last three lines run the function:
    • IO.puts prints the results of addition (8) and multiplication (28).
    • IO.inspect prints the error tuple {:error, "Cannot divide by zero"} in a readable form.

This approach keeps the logic simple and readable. Instead of one function with many if/else statements, each operation becomes a clear, separate clause that matches a specific input shape.

Beyond Tuples: Matching Lists, Maps, Structs, and Binaries

Pattern matching in function heads is not limited to tuples. You can match on lists, maps, structs, and binaries, allowing for highly expressive and safe APIs.

Matching Lists

Explanation:

  • The base case sum([]) matches an empty list and returns 0, stopping recursion.
  • The recursive case sum([head | tail]) uses the [head | tail] pattern to split a non-empty list. head is the first element, and tail is the rest of the list.
  • This pattern is the standard way to traverse and process collections in functional programming.
Matching Maps

Explanation:

  • Map matching is "partial": the pattern % {name: name} matches any map that has a :name key, even if it contains dozens of other keys.
  • You can match literal values (like :en or :es) alongside variables (like name).
  • The last clause greet(%{name: name}) acts as a fallback for any map that has a :name key but doesn't match the specific languages above.
Matching Structs

Explanation:

  • Struct matching uses the %ModuleName{} syntax.
  • Unlike maps, matching against %User{} ensures that the input is specifically a User struct and not a plain map with the same keys.
  • can_edit?(%User{role: :admin}) matches only when the role is exactly :admin, while can_edit?(%User{}) matches any other User struct.
Matching Binaries

Explanation:

  • Binaries (and strings) can be matched using the << >> syntax.
  • <<"GIF8", _rest::binary>> matches any binary that starts with the literal string "GIF8".
  • The ::binary modifier tells Elixir that _rest can be any sequence of bits/bytes of any length following the prefix. This is commonly used for parsing file headers or network protocols.
Failure Modes: FunctionClauseError and Safe Fallbacks

If no function clause matches, Elixir raises a FunctionClauseError. To avoid this, provide a catch-all clause to handle unexpected inputs safely.

Overlapping Clauses, Order, and Guards

Order matters: Elixir matches clauses from top to bottom. More specific patterns (or those with guards) should come before more general ones.

Elixir matches the structural pattern first; if it matches, the guard is then evaluated. When patterns overlap, order still decides which clause is tried first; a failing guard causes Elixir to continue trying later clauses.

Guards can make clauses more precise, but they don’t change which clause is tried first—order still governs which structural pattern is tested before guards are evaluated.

Summary and Next Steps

You learned how to:

  • Match tuple shapes in function heads to dispatch logic (Calculator).
  • Use guards to protect clauses (b != 0) and add safe fallbacks for invalid inputs.
  • Reason about clause order and overlapping patterns; Elixir matches structure first, then evaluates guards, and continues to later clauses when a guard fails.
  • Match lists, maps, structs, and binaries in function heads.

This pattern is common in real Elixir codebases because it is fast to read, safe, and extensible. Ready to solidify this skill? Head over to the practice section and try it out.

Sign up
Join the 1M+ learners on CodeSignal
Be a part of our community of 1M+ users who develop and demonstrate their skills on CodeSignal