Variables and Immutability
Why Elixir data never mutates in place, and what that means for how you write everyday code.
3 min read
Elixir has variables, and you assign them with = just like most languages:
count = 1
name = "Ada"But under the hood, something different is happening than in a language like JavaScript or Python: the values Elixir variables point to are immutable. Once a piece of data is created, it can never be changed in place — only new data can be created from it.
Rebinding, not mutating
You can absolutely reassign a variable to a new value:
count = 1
count = count + 1
IO.puts(count)
# => 2But this isn't mutation — it's rebinding. The name count now points to a brand-new integer 2; the original 1 still exists conceptually, it's just no longer referenced by that name. This distinction matters more once you pass data into functions.
Data passed to a function is never changed by it
defmodule ListTools do
def add_item(list, item) do
[item | list]
end
end
original = [2, 3]
updated = ListTools.add_item(original, 1)
IO.inspect(original)
# => [2, 3]
IO.inspect(updated)
# => [1, 2, 3]add_item/2 didn't and can't modify original — it returns a new list built from the old one. original is completely untouched after the call. This eliminates an entire category of bugs common in mutable languages: a function you call unexpectedly changing data you're still holding a reference to elsewhere.
Why immutability is worth the adjustment
It takes a little getting used to if you're coming from mutable languages, but immutability is what makes Elixir's concurrency model safe. If ten processes are all handed a reference to the same list, none of them can corrupt it for the others — there's no shared mutable state to protect with locks, because there's no mutation at all. That safety is a big part of why the BEAM can run so many processes concurrently without the defensive machinery (mutexes, locks, careful synchronization) that mutable-state languages need.
The pin operator: ^
Normally, = in Elixir isn't assignment in the traditional sense — it's a match operator (more on this in the next lesson on pattern matching). By default, rebinding a variable name on the left of = is allowed:
x = 1
x = 2 # fine, rebinds xBut sometimes you want to assert that a variable equals an existing value rather than rebind it. That's what the pin operator ^ is for:
x = 1
^x = 1 # matches successfully, x stays 1
^x = 2 # raises a MatchError -- x is 1, not 2You won't use ^ constantly, but it's essential when pattern matching against a variable's current value rather than silently rebinding it — a scenario the next lesson covers in more depth.
Practical takeaway
Write Elixir code assuming every function call returns something new rather than changing its inputs. It reads differently than the mutable style you might be used to — more like a pipeline of transformations than a sequence of edits — and that shift in thinking is one of the most important habits to build early in learning the language.
Test what you just learned
4 quick questions. Get all of them right to unlock the next lesson.
You can take the quiz without an account — logging in just lets your result count toward your progress.