Pattern Matching

Elixir pattern matching with = operator, tuples, lists, maps, and case/with

Pattern matching is the foundation of Elixir. The = operator is not assignment — it’s a match operator that asserts the left side equals the right side, binding variables in the process.

The match operator

x = 1          # binds x to 1
1 = x          # succeeds because x is 1
2 = x          # ** (MatchError) no match of right hand side value: 1

Matching tuples

{:ok, value} = {:ok, 42}
value  # => 42

{:error, reason} = {:ok, 42}  # ** (MatchError)

Matching lists

[head | tail] = [1, 2, 3]
head  # => 1
tail  # => [2, 3]

[first, second, third] = [10, 20, 30]
first  # => 10

Matching maps

%{name: name} = %{name: "Alice", age: 30}
name  # => "Alice"

# Variable keys must be pinned
key = :name
%{^key => value} = %{name: "Alice"}
value  # => "Alice"

Pin operator ^

Prevent re-binding a variable in a match by pinning it:

x = 1
^x = 2  # ** (MatchError)
x = 2   # re-binds x to 2

case

Match a value against multiple patterns:

case File.read("config.txt") do
  {:ok, contents} -> IO.puts(contents)
  {:error, :enoent} -> IO.puts("File not found")
  {:error, reason} -> IO.puts("Error: #{inspect(reason)}")
end

with

Chain pattern matches, handling failures gracefully:

with {:ok, user} <- fetch_user(id),
     {:ok, post} <- fetch_post(user),
     {:ok, html} <- render_post(post) do
  {:ok, html}
else
  {:error, :not_found} -> {:error, "Not found"}
  {:error, reason} -> {:error, reason}
end

Function heads

Define multiple clauses of a function with different patterns:

defmodule Router do
  def handle(%{method: "GET", path: "/"}), do: :home
  def handle(%{method: "GET", path: "/users"}), do: :users
  def handle(%{method: "POST", path: "/users"}), do: :create_user
  def handle(_), do: :not_found
end