Operators in Elixir
Explore arithmetic, comparison, boolean, and functional operators in Elixir, including the pipe operator, match operator, and list operations
Operators in Elixir are not just syntactic sugar for arithmetic — they are the everyday vocabulary you use to compose functional pipelines, match values, and transform immutable data. As a functional language running on the BEAM, Elixir gives operators a distinct flavor: the equals sign is not assignment but pattern matching, the pipe operator threads values through transformations, and there are two flavors of boolean logic (strict and relaxed) depending on whether you want truthy semantics or strict boolean checks.
This tutorial walks through the operators you will reach for in nearly every Elixir program. We will look at arithmetic, comparison, boolean logic, string and list operators, and the two operators that define the Elixir style most strongly — the match operator (=) and the pipe operator (|>).
Because Elixir values are immutable, none of these operators mutate their operands. Each expression returns a new value, which is at the heart of how functional pipelines stay safe to reason about.
Arithmetic Operators
Elixir provides the familiar arithmetic operators plus a few distinctions that matter for integer math. The / operator always returns a float, even when both operands are integers. For integer division and remainder, Elixir uses the div/2 and rem/2 functions instead of a dedicated operator.
Create a file named arithmetic.exs:
| |
Notice that 17 / 5 produces 3.4, not 3. If you want integer division, reach for div/2. Elixir 1.13+ also has a ** operator for exponentiation, but :math.pow/2 from the underlying Erlang math module is the historically portable choice and always returns a float.
Comparison and Equality
Elixir has both relaxed equality (==) and strict equality (===). The relaxed form treats 1 and 1.0 as equal, while the strict form distinguishes integers from floats. Both forms have negation counterparts (!= and !==).
Create a file named comparison.exs:
| |
One quirk worth knowing: Elixir defines a total ordering across all types, so you can compare a number to an atom or a string without an error. The ordering is: number < atom < reference < function < port < pid < tuple < map < list < bitstring. You rarely rely on this directly, but it means sorts and comparisons never crash on mixed types.
Boolean Operators: Strict vs Relaxed
Elixir has two families of boolean operators. The strict forms — and, or, not — require their left operand to be a strict boolean (true or false). The relaxed forms — &&, ||, ! — accept any value, treating nil and false as falsy and everything else as truthy.
Create a file named booleans.exs:
| |
The relaxed forms are how you express defaults and guards on potentially-nil values. They also short-circuit, so expensive_call() || fallback() will skip the fallback when the first call returns a truthy value.
String, List, and the Pipe Operator
Elixir uses dedicated operators for concatenating strings (<>), concatenating lists (++), and subtracting list elements (--). The match operator (=) binds names to values via pattern matching, and the pipe operator (|>) feeds the result of one expression as the first argument to the next function call — the defining shape of idiomatic Elixir code.
Create a file named operators.exs:
| |
The match operator is where Elixir most clearly departs from imperative languages. {x, y, z} = {10, 20, 30} does not assign three variables — it asserts that the left side matches the right side and binds any unbound variables in the pattern. If the shapes do not match, you get a MatchError.
The pipe operator is the workhorse of Elixir style. value |> f(arg) is equivalent to f(value, arg). This lets you read data transformations top-to-bottom rather than inside-out, which fits the functional emphasis on shaping data through composed functions.
Running with Docker
| |
Expected Output
Running arithmetic.exs:
a + b = 22
a - b = 12
a * b = 85
a / b = 3.4
div(a, b) = 3
rem(a, b) = 2
-a = -17
2 ** 10 = 1024.0
Running comparison.exs:
1 == 1.0 => true
1 === 1.0 => false
1 != 2 => true
1 !== 1.0 => true
3 < 5 => true
5 <= 5 => true
"abc" < "abd" => true
1 < :atom => true
:atom < "s" => true
Running booleans.exs:
true and false => false
true or false => true
not true => false
nil || "default": "default"
"first" || "second": "first"
nil && "never": nil
"hello" && "world": "world"
!nil: true
Running operators.exs:
Hello, World!
combined: [1, 2, 3, 4, 5]
trimmed: [1, 3, 5]
x=10, y=20, z=30
head=1
tail: [2, 3, 4]
piped result: HELLO-WORLD
3 in [1, 2, 3] => true
9 in 1..5 => false
Key Concepts
/always returns a float — usediv/2andrem/2for integer division and remainder- Two equality operators —
==is relaxed (1 == 1.0is true),===is strict (1 === 1.0is false) - Strict vs relaxed booleans —
and/or/notrequire true booleans;&&/||/!accept any value and treatnilandfalseas falsy - Short-circuiting returns values —
nil || "default"returns"default", not justtrue =is the match operator — it pattern-matches the right side against the left, binding unbound names- Pipe operator threads data —
value |> f(arg)becomesf(value, arg), enabling readable functional pipelines - Dedicated operators per type —
<>for strings,++/--for lists; mixing them up is a type error rather than implicit coercion - Total ordering across types — any two terms can be compared with
</>, which keeps generic sorts safe on mixed data
Running Today
All examples can be run using Docker:
docker pull elixir:1.17-alpine
Comments
Loading comments...
Leave a Comment