Functions in Elixir
Learn how to define and use functions in Elixir - named and anonymous functions, multiple clauses, guards, recursion, and higher-order functions with Docker-ready examples
In Elixir, functions are the fundamental unit of code. As a functional language, Elixir treats functions as first-class values: you can bind them to variables, pass them as arguments, return them from other functions, and store them in data structures. There are no objects with methods here—just functions that transform immutable data into new data.
Elixir has two kinds of functions. Named functions live inside modules and are defined with def. Anonymous functions are values you can create on the fly with fn or the capture operator &. Understanding the difference between them—and how they are called—is essential.
Because Elixir is built on the Erlang VM and embraces immutability, it has no traditional loops. Iteration is expressed through recursion and higher-order functions like Enum.map/2. Pattern matching in function heads and guards let you write multiple clauses that respond to different inputs without if statements. This tutorial walks through each of these ideas with runnable examples.
Named Functions
Named functions must be defined inside a module using def (public) or defp (private). A function can use a full do...end block, or the do: shorthand for one-liners. The last expression in the body is returned automatically—there is no return keyword.
Create a file named functions_basic.exs:
| |
Functions are identified by both their name and their arity (number of arguments), written as add/2 or square/1. Two functions with the same name but different arity are completely separate functions.
Default Parameters
Elixir supports default argument values using the \\ operator. If the caller omits an argument, the default is used.
Create a file named default_params.exs:
| |
The first call uses the default "Hello", while the second supplies its own greeting.
Anonymous Functions and the Capture Operator
Anonymous functions are values created with fn ... end. Note that calling them requires a dot before the parentheses: double.(10), not double(10). This explicit dot syntax distinguishes anonymous function calls from named function calls. The capture operator & provides a compact shorthand, and can also capture existing named functions so they can be passed around as values.
Create a file named anonymous.exs:
| |
Anonymous functions are closures: they capture the variables in scope at the point where they are defined. Each function has its own local scope—variables defined inside one function clause are not visible to others.
Recursion with Multiple Clauses and Guards
Without loops, Elixir uses recursion for repeated work. Recursion combines naturally with multiple function clauses and guards (the when keyword). Elixir tries each clause from top to bottom and runs the first one whose pattern and guard match.
Create a file named recursion.exs:
| |
The base case (factorial(0)) stops the recursion, while the guarded clause handles every positive integer. This pattern—a base case plus a recursive case—is the functional replacement for an imperative loop.
Higher-Order Functions and the Pipe Operator
Because functions are values, you can pass them to other functions. The Enum module is full of such higher-order functions, and the pipe operator |> chains them by feeding the result of one call as the first argument to the next.
Create a file named higher_order.exs:
| |
Reading top to bottom: 1..5 produces [1, 2, 3, 4, 5], Enum.map/2 squares each to [1, 4, 9, 16, 25], Enum.filter/2 keeps the odd values [1, 9, 25], and Enum.sum/1 adds them to 35. The pipe operator turns nested function calls into a readable, left-to-right pipeline.
Running with Docker
Run each example with the official Elixir image—no local install required:
| |
Expected Output
7
25
Hello, World!
Welcome, Elixir!
20
30
ELIXIR
120
3628800
Sum of odd squares from 1..5: 35
Key Concepts
- Functions are first-class values — bind them to variables, pass them as arguments, and return them from other functions.
- Named vs. anonymous — named functions (
def) live in modules and are called likeCalculator.add(3, 4); anonymous functions (fn/&) are called with a dot:double.(10). - Arity matters — a function is identified by name and number of arguments (
factorial/1); same name with different arity means a different function. - Implicit return — the last expression in a function body is its return value; there is no
returnkeyword. - Multiple clauses and guards — pattern matching in the function head plus
whenguards replace manyif/switchstatements. - Recursion replaces loops — a base case plus a recursive case is the idiomatic way to iterate in a functional language.
- The capture operator
&— a concise shorthand for anonymous functions (&(&1 * 3)) and a way to turn named functions into values (&String.upcase/1). - Higher-order functions and
|>— functions likeEnum.map/2accept other functions, and the pipe operator chains transformations into readable pipelines.
Running Today
All examples can be run using Docker:
docker pull elixir:1.17-alpine
Comments
Loading comments...
Leave a Comment