Pattern matching is a fundamental feature in Elixir, making your code efficient, readable, and intuitive and easing your cognitive load during development overall. If you’re looking for a way to replace the traditional if statements, pattern matching offers developers a smart and elegant way to do so.

Pattern matching in Elixir works with diverse data structures. Be it dealing with lists, tuples, or maps … so, let’s take a closer look!
Pattern Matching: What it is and how to use it
Pattern matching in Elixir is the process of comparing a value against a pattern. It is used to destructure data structures and bind variables to their contents. Let’s look at some simple examples:
match?(1, 1)This is probably the most simple (and boring) example of a pattern match. When executed in iex, the result is true.

match?/2 will „match“ the two parameters given to it, that is, determine if they are „the same“, and return true if that is the case and false otherwise. The interesting question is, of course, what „the same“ means. Let’s try something that surely is not “the same”.

And sure enough, 1 is not „the same“ as 2.
At its core, that is what pattern matching is. Compare two things and do something if they are „the same“.
Let´s continue with more complex examples:
Matching a Simple Tuple:
{a, b} = {1, 2}
IO.puts(a) # Outputs
1 IO.puts(b) # Outputs 2In this example, the tuple {1, 2} is matched against the pattern {a, b}, binding a to 1 and b to 2.
Matching Head and Tail of a List:
[h | t] = [1, 2, 3]
IO.puts(h) # Outputs 1
IO.inspect(t) # Outputs [2, 3]Here, [h | t] matches the head of the list to h and the tail to t.
Matching with Maps:
%{name: name, age: age} = %{name: "Alice", age: 30}
IO.puts(name) # Outputs "Alice"
IO.puts(age) # Outputs 30This example matches the keys :name and :age in the map, binding their corresponding values to name and age, respectively.
With that in mind, a definition of what pattern and pattern matching are can be made:
A pattern describes the structure of data.
Pattern matching is the process of determining if a concrete value is structured as described by a given pattern.
The Power of Pattern Matching

Pattern matching is crucial in control flow structures like the case statement, allowing for more readable and maintainable code. The case statement evaluates the expression and matches it against various patterns. Here’s an example:
case {1, 2, 3} do
{1, x, 3} -> "Matched: #{x}"
_ -> "No match"
end
# Outputs "Matched: 2"In this example, the tuple {1, 2, 3} is matched against the pattern {1, x, 3}, binding x to 2 and returning „Matched: 2“. If the pattern doesn’t match, the fallback clause _ handles it, returning „No match“.
Match as Case
Pattern matching in Elixir can be used instead of conventional branching logic, such as if or case. This reduces complexity and enhances readability.
Consider this function, which deduces potential responses based on a user’s status:
def respond_to_user(%{status: :active}) do
"Welcome back!"
end
def respond_to_user(%{status: :inactive}) do
"Please activate your account."
end
def respond_to_user(_) do
"Unknown user status."
endInstead of using a series of if-else statements, the function bodies with different patterns handle various cases based on user status.
Variable Binding
As mentioned above, variable binding is another powerful aspect of pattern matching. It binds values extracted from complex data structures directly to variables:
Matching a tuple:
{a, b} = {3, 4}
IO.puts(a) # Outputs 3
IO.puts(b) # Outputs 4Binding elements from a map:
%{name: person_name, age: person_age} = %{name: "Alex", age: 25}
IO.puts(person_name) # Outputs "Alex"
IO.puts(person_age) # Outputs 25Pattern matching simplifies conditionally assigning variables and makes the code cleaner and more intuitive.
Monads Made Easy

What is a Monad?
In programming, a Monad is a design pattern used to handle a variety of computational contexts, such as dealing with nullability, handling state, or consistently managing side effects. While Elixir does not have monads in the same way as some other functional languages (like Haskell), it offers constructs that achieve similar goals, namely, clean chaining of operations and handling of results.
Elixir’s with statement simplifies complex nested operations by allowing you to chain multiple pattern matches and assign operations sequentially. Let’s see an example:
Suppose you need to fetch user data, verify the user’s status, and authorize them – a typical flow in many applications. Here’s how you can do it using with:
with
{:ok, user} <- fetch_user(user_id),
{:ok, verified_user} <- verify_user(user),
{:ok, authorized_user} <- authorize_user(verified_user) do
{:ok, authorized_user}
else
error -> {:error, error}
endIn this example:
fetch_user/1attempts to fetch a user and returns either{:ok, user}or an error tuple.verify_user/1returns{:ok, verified_user}or an error tuple.authorize_user/1returns{:ok, authorized_user}or an error tuple.
If any of these functions return an error, the error -> {:error, error} clause in else will handle it. This construct allows you to sequence operations and make your code more linear and easier to follow.
Chaining Operations
Elixir’s pattern matching coupled with the with statement helps in chaining operations seamlessly. This ensures a smooth flow of data transformations without deeply nested code blocks.
Consider a scenario where you perform a series of operations on a list of numbers, such as doubling each number and then summing the results. You can chain these operations like so:
def process_numbers do
numbers = [1, 2, 3]
doubled = Enum.map(numbers, fn x -> x * 2 end)
sum = Enum.reduce(doubled, 0, &(&1 + &2))
sum
endAlternatively, using the with construct here enhances clarity when dealing with intermediate steps or potential errors:
def process_numbers(numbers) do
with
doubled <- Enum.map(numbers, &(&1 * 2)),
sum <- Enum.reduce(doubled, 0, &(&1 + &2)) do
sum
else
_ -> {:error, "Processing failed"}
endThe with construct provides a structured yet flexible way to manage sequences of operations, ensuring explicit error handling and streamlined data flow.
Using with and chaining techniques help to keep Elixir code concise and improve readability, making complex logic more straightforward to manage and debug.
Functions in Elixir

Multiple Function Clauses
One of the strengths of Elixir is its support for multiple function clauses, allowing developers to define clear, concise, and readable functions to handle different scenarios. This is often accomplished using pattern matching directly in function definitions.
Single Function Definition
Typically, functions are written to handle general cases, often resulting in complex nested conditionals. However, with multiple function clauses, each function variant can be as simple or complex as needed, directly addressing specific patterns.
Multiple Function Clauses with Pattern Matching
Elixir allows you to define multiple function clauses that match against different patterns. This feature is particularly useful when handling different cases explicitly without complex conditionals. Here’s an example of a function that greets users based on their status:
def greet(%{status: :active, name: name}) do
"Welcome back, " <> name <> "!"
end
def greet(%{status: :inactive, name: name}) do
"Please activate your account, " <> name <> "." end def greet(_) do "Hello, guest!"
endIn this example:
- The first
greetfunction clause matches a map with the key:statusset to:activeand extracts the user’s name, returning a personalized welcome message. - The second
greetfunction clause matches a map with the key:statusset to:inactiveand also extracts the user’s name, prompting them to activate their account. - The third
greetfunction clause is a default case that matches any other inputs, providing a generic greeting.
Recursive Functions with Multiple Clauses
Elixir’s support for recursion works seamlessly with multiple function clauses. This combination simplifies loops and repetitive tasks. Consider a function that sums all elements in a list:
def sum_list([]) do 0
end
def sum_list([head | tail]) do
head + sum_list(tail)
endIn this example:
- The first
sum_listfunction matches an empty list and returns0. - The second
sum_listfunction matches a non-empty list, decomposes it intoheadandtail, and recursively sums the elements.
Error Handling with Multiple Function Clauses
Multiple function clauses can also streamline error handling by defining specific behaviors for different error cases. Consider a function that parses configuration options:
def parse_config(%{key: value}) when is_integer(value) do {:ok, value}
end
def parse_config(%{key: value}) when is_binary(value) do
{:ok, String.to_integer(value)}
end
def parse_config(_) do
{:error, "Invalid configuration"}
endIn this example:
- The first
parse_configfunction clause matches a map with an integer value and returns it as{:ok, value}. - The second
parse_configfunction clause matches a map with a string value, converts it to an integer, and then returns it. - The third
parse_configfunction clause is a fallback that handles any other cases, returning an error tuple.
You can create efficient, clean, and expressive functions that naturally handle various cases in Elixir by utilising multiple function clauses. This makes code easier to read and maintain while adhering to functional programming principles. So let’s go!
You want to learn more about Elixir and what it has to offer? How about taking a look at Elixir Sigils?






