One of the most common points of confusion for developers new to Elixir (and the BEAM VM) is how process messaging actually works—especially around timeouts, mailboxes, and whether processes “keep running forever”.
This article clears that confusion step by step.
We’ll cover:
- How messages are delivered and stored
- What
receive … afterreally does - Whether messages are lost after timeouts
- When processes run infinitely (and when they don’t)
- Whether you can read all messages later
- What happens on crashes and exits
- How OTP supervisors fit into all this
1. Elixir Process Basics (The Foundation)
An Elixir process is:
- Extremely lightweight
- Isolated (no shared memory)
- Communicates only via message passing
- Owns a private mailbox
Each process has:
- A mailbox (queue of messages)
- A heap (its state)
- An execution loop (your code)
Messages are sent using:
send(pid, message)
Messages are delivered asynchronously and stored in the receiver’s mailbox.
2. How Messages Are Processed
Messages are processed using receive:
receive do
msg -> handle(msg)
end
Important rules:
- Messages are not pulled automatically
- A process must explicitly call
receive receivescans the mailbox from oldest to newest- Only the first matching message is consumed
- Unmatched messages stay in the mailbox
3. What Happens When a Timeout Is Used?
Consider this code:
receive do
msg -> handle(msg)
after
3_000 ->
:timeout
end
Key clarification
The timeout only ends the
receiveblock — not the process.
What actually happens:
- The process waits up to 3 seconds
- If no matching message arrives:
- The
afterclause runs
- The
- Execution continues to the next line of code
Messages are NOT dropped
If a message arrives after the timeout:
send(pid, :hello)
That message:
- Goes into the mailbox
- Is not discarded
- Can be received later
Timeouts do not affect message delivery.
4. So Why Do Messages Sometimes “Disappear”?
Messages are lost only if the process is no longer alive.
Example:
receive do
msg -> handle(msg)
after
3_000 -> :timeout
end
# process exits here
If the process exits:
- Mailbox is destroyed
- Late messages are lost
📌 Timeout ≠ process exit
📌 Process exit = mailbox destruction
5. Do Processes Run Infinite Loops?
Only if you write a loop.
No loop → process exits
receive do
msg -> handle(msg)
end
After one message:
- Function ends
- Process exits
Loop → process stays alive
def loop do
receive do
msg -> handle(msg)
after
3_000 -> :idle
end
loop()
end
Now the process:
- Keeps receiving messages forever
- Survives timeouts
- Can receive late messages
📌 Infinite behavior is explicit, not implicit.
6. Can You Access All Messages Later?
Short answer: No
Important BEAM guarantees:
- Mailboxes are private
- Messages are destructively read
- Once consumed, messages are gone forever
- You cannot inspect another process’s mailbox
This is intentional, to preserve:
- Isolation
- Determinism
- Fault tolerance
What you can do
- Drain your own mailbox
- Log messages explicitly
- Forward messages to an observer
- Persist events externally (DB, ETS, Kafka, etc.)
If you need replayability, you need an event system, not a mailbox.
7. What If a Message Doesn’t Match Any Pattern?
Example:
receive do
{:ok, msg} -> handle(msg)
end
If the mailbox contains:
:error
{:ok, 42}
What happens?
:errordoes not match- It stays in the mailbox
{:ok, 42}is consumed
📌 Unmatched messages stay forever unless handled.
This can cause:
- Mailbox growth
- Performance degradation
- “Mailbox poisoning”
Best practice
Always include a catch-all:
receive do
known -> handle(known)
unexpected -> log(unexpected)
end
8. What Happens on Crashes and Exits?
If a process crashes:
- Mailbox is destroyed
- State is lost
- PID becomes invalid
- Messages sent afterward are lost
This is why Elixir embraces:
Let it crash
Instead of defensive code, Elixir relies on supervision.
9. Where OTP Supervisors Come In
A GenServer or supervised process:
- Runs an internal infinite loop
- Is restarted automatically on crash
- Rebuilds state in
init/1 - Keeps message handling safe
Supervisors:
- Trap exits
- Monitor child processes
- Restart them based on strategy
Restarted processes are brand new — state must be:
- Reconstructed
- Loaded from DB
- Externalized
10. The Correct Mental Model
Think of an Elixir process as:
A worker sitting in a room with a mailbox
- Messages arrive anytime
receive= checking the mailbox- Timeout = “I’ll stop checking for now”
- Loop = staying in the room
- Exit = room destroyed
Final Takeaways (TL;DR)
- Timeouts do not drop messages
- Messages always go to the mailbox if the process is alive
- Processes do not loop automatically
- Infinite behavior requires explicit loops or GenServer
- Unmatched messages stay in the mailbox
- Mailboxes cannot be inspected or replayed
- Crashes destroy state and mailbox
- Supervisors restart clean processes
One-line summary
Elixir processes only receive messages while alive and looping; timeouts stop waiting, not message delivery, and OTP supervision exists to manage crashes safely.