Immutability

In most languages, changing a variable means overwriting the value that used to live there. Elixir does not work that way. In Elixir, values are immutable: once a value exists it cannot be changed. You can bind the same name to a new value, but the old value is still sitting untouched in memory (until it is eventually garbage collected).

Why does that matter?

Because immutable values cannot change underneath you, multiple processes can look at the same value at the same time without any locking. That single property is why Phoenix can comfortably spread a workload across every CPU on a server, and why Elixir clusters can share state across machines without the usual concurrency headaches.

You might reasonably ask: "does all this copying make my program slower?" In practice, no. The runtime shares structure between old and new values instead of copying everything, and avoiding mutation lets the compiler and scheduler take shortcuts that a traditional imperative runtime cannot.

What it looks like in code

Every time you use a variable, you get the value from the moment it was bound. Rebinding the name later does not retroactively change functions that captured the old value:

iex> product = "Orange"
"Orange"
iex> test1 = fn -> IO.puts(product) end (1)
#Function<43.113135111/0 in :erl_eval.expr/6>
iex> product = "Apple"
"Apple"
iex> test2 = fn -> IO.puts(product) end
#Function<43.113135111/0 in :erl_eval.expr/6>
iex> product = "Pineapple"
"Pineapple"
iex> test3 = fn -> IO.puts(product) end
#Function<43.113135111/0 in :erl_eval.expr/6>
iex> product = "Banana"
"Banana"
iex> test1.() (2)
Orange
:ok
iex> test2.()
Apple
:ok
iex> test3.()
Pineapple
:ok
iex> IO.puts(product)
Banana
:ok
1 Each of those anonymous functions captures the current value of product. They can run on totally different CPUs and each one lives in its own little universe.
2 product has been rebound three more times since we created test1, but test1.() still prints "Orange" because that is the value it captured.
You do not need to fully understand immutability to start learning Elixir. Just keep the idea in the back of your mind. It will click over time.
Agentic Coding Tip: Elixir Functions Return New Values — They Don’t Mutate

An agent trained primarily on JavaScript, Python, or Ruby will instinctively reach for in-place mutation. When you ask for "add an item to the shopping list", Claude often writes something like:

# WRONG — this doesn't exist
shopping_list.push("pineapple")

or worse, assumes that assigning to a "field" of a map changes the original:

# This does NOT change `user`. It creates a new map.
Map.put(user, :verified, true)

Elixir doesn’t have that kind of mutation. Map.put/3 returns a new map. The original user is untouched. You use the return value or assign the name to the new value:

iex> user = %{name: "Ada", verified: false}
iex> user = Map.put(user, :verified, true)
%{name: "Ada", verified: true}

The same applies to lists ([head | tail] builds a new list), to structs (%User{user | verified: true}) and to any compound value. If you do not use the return value, the "change" is discarded.

Rule to add to your project’s CLAUDE.md:

Elixir values are immutable. Functions like Map.put/3,
List.insert_at/3, Enum.map/2 return a new value — they
never mutate the input. Always bind the return value (or
pipe it into the next step). If you find yourself writing
code that "modifies" a struct, map or list in place, stop
— that's not how Elixir works. Suggest rewriting with
`Map.put/3`, `List.replace_at/3`, update syntax
(`%{m | key: value}`) or a pipe.