March 9th, 2026
At the end of the last post, I used a multi-clause function to demonstrate the power of pattern matching in Elixir. What are multi-clause functions though, and how is this even possible?
In many languages, a function is defined by name only, and you can only have one function with a given name per class or namespace.
For example, if you redeclare the same greet() function twice in PHP, it will blow up with a FATAL ERROR:
class Message
{
public function greet(User $user): string
{
return "Welcome, {$user->name}!";
}
public function greet(Contact $contact): string
{
return "Welcome, {$contact->displayName}!";
}
}
# FATAL ERROR - Cannot redeclare Message::greet()
As we've learned though, Elixir allows for pattern matching across multiple function clauses with the same name, where you can define the various shapes of data that a function can accept:
defmodule Message do
def greet(%User{} = user) do
"Welcome, #{user.name}!"
end
def greet(%Contact{} = contact) do
"Welcome, #{contact.display_name}!"
end
end
This is totally valid Elixir. When calling Message.greet(contact) with a %Contact{} struct as an argument, Elixir will use pattern matching to pass over the first %User{} clause, running the second clause instead.
As long as the shape of the arguments is distinct enough to pattern match without ambiguity, Elixir will treat both of these as valid compilable function clauses.
More than just the shape of the arguments, Elixir also considers the number of arguments in any given function definition. This is called arity, and you'll often see this referenced as function_name/arity:
# This is `greet/1` (because it has one argument)
def greet(%User{} = user) do
"Welcome, #{user.name}!"
end
# This is `greet/2` (because it has two arguments)
def greet(%User{} = user, %Time{} = time) do
cond do
time.hour in 0..11 -> "Good morning, #{user.name}!"
time.hour in 12..17 -> "Good afternoon, #{user.name}!"
time.hour in 18..23 -> "Good evening, #{user.name}!"
end
end
Now if we call greet(user, time) with two arguments, Elixir will know to run the second greet/2 clause because of function arity.
The important thing to note here is that a function's identity is both its name and its arity — so greet/1 and greet/2 are totally separate functions, not overloads of the same function. This mainly matters for control flow, but there are more uses for arity which we'll cover in a future post.
You can see how pattern matching and arity can really clean things up, but what about that nested cond expression inside my greet/2 clause? If we want to remove a layer of nesting here, we can extract to the top level using when guard expressions:
def greet(%User{} = user, %Time{hour: h}) when h in 0..11 do
"Good morning, #{user.name}!"
end
def greet(%User{} = user, %Time{hour: h}) when h in 12..17 do
"Good afternoon, #{user.name}!"
end
def greet(%User{} = user, %Time{hour: h}) when h in 18..23 do
"Good evening, #{user.name}!"
end
Finally, maybe we also want to have a simple fallback greet/0 clause for when we don't have person or time arguments to pass:
def greet do
"Greetings!"
end
Now why does all of this really matter? 👀
In PHP, if you wanted a greet() function to handle all of the edge cases we've touched on, you would have to get clever with nested conditions and guards inside your function, maybe something like this:
public function greet(User|Contact|null $person = null, ?DateTime $time = null): string
{
if (! $person) {
return "Greetings!";
}
$hour = $time?->format('G');
if ($time && $hour >= 0 && $hour <= 11) {
$greeting = "Good morning";
} elseif ($time && $hour >= 12 && $hour <= 17) {
$greeting = "Good afternoon";
} elseif ($time && $hour >= 18 && $hour <= 23) {
$greeting = "Good evening";
} else {
$greeting = "Welcome";
}
if ($person instanceof User) {
$name = $person->name;
} elseif ($person instanceof Contact) {
$name = $person->displayName;
}
return "{$greeting}, {$name}!";
}
This honestly isn't so bad! You could even clean this up by extracting smaller getGreeting($time) and getName($person) helpers, but the complexity of logic required within a single greet() definition as an entry point remains.
Elixir, on the other hand, lets you flatten greet() into simpler, hyperfocused function clauses:
def greet(person, %Time{hour: h}) when h in 0..11 do
"Good morning, #{name(person)}!"
end
def greet(person, %Time{hour: h}) when h in 12..17 do
"Good afternoon, #{name(person)}!"
end
def greet(person, %Time{hour: h}) when h in 18..23 do
"Good evening, #{name(person)}!"
end
def greet(person) do
"Welcome, #{name(person)}!"
end
def greet do
"Greetings!"
end
defp name(%User{} = user) do
user.name
end
defp name(%Contact{} = contact) do
contact.display_name
end
Using all of these multi-clause techniques together (pattern matching, arity, and guard expressions), our clauses are now:
Don't. Sleep. On. Elixir. 😎
—Thanks for reading!
For updates, follow me on Twitter / X or subscribe via RSS.
© Jesse Leite