Station 02Open the adventure map
Processes and mailboxes
Each process keeps its own data. When processes need to work together, they send messages.
When several senders update a counter at once, who owns the state, and how can we spot messages that the mailbox never handles?
The counter is alive, but read requests get no answer
The counter process is still running and senders keep delivering messages, but the reported value stops changing. Its loop forgot one message shape, so unmatched messages keep collecting in the mailbox.
- Whether the counter PID is still alive
- Whether the mailbox length keeps growing
- The value that stopped changing and the actual shape of unmatched mailbox messages
The problem it solves
BEAM lets many small processes work at once. Each process owns its data and works with others through messages. When one process fails, the effect can usually stay in one small area.
You will be able to
Tell the difference between a small BEAM process, an operating-system process, and a thread
Explain how each process owns its data and receives messages from its mailbox
Read a message loop and predict its next state and pattern-match result
- Finish the starting line and successfully run a short Erlang or Elixir program
- Have seen tuples, lists, and maps. It is fine if you do not remember every spelling yet
See what it does before how it is written
Send one tiny message first to identify PIDs, send, and receive. Then see how the counter carries new state into another round instead of changing an old value in place.
# Remember the current IEx process so the worker has a return address
parent = self()
# spawn starts an isolated process and immediately returns its PID
worker =
spawn(fn ->
# The child puts its own PID in a message and sends it to parent
send(parent, {:hello, self()})
end)
# The parent waits in its mailbox; ^worker checks who replied
receive do
{:hello, ^worker} -> :worker_replied
end
# total is the state currently owned by the counter process
counter = fn loop, total ->
receive do
# Enter the next round with a new total; the old value is not changed in place
{:add, n} -> loop.(loop, total + n)
{:read, caller} ->
send(caller, {:total, total})
loop.(loop, total)
end
end
# Start the counter process with 0 as its first value
pid = spawn(fn -> counter.(counter, 0) end)Why this shape?
Why not let every worker mutate one shared object?
One process owns the state; everyone else sends messages. Update order and failure boundaries become explicit.
Processes and messages have a cost. Pure computation without state, lifecycle, or isolation is often clearer as a function, and a process is not automatically faster.
Coming from Java, Python, or JavaScript
Java
- Familiar starting point
- Threads, virtual threads, and shared objects.
- What BEAM changes
- BEAM processes isolate memory by default and exchange terms through mailboxes.
- False friend
- Modern Java has virtual threads; state ownership and failure semantics remain the useful comparison.
Python
- Familiar starting point
- threading, multiprocessing, or asyncio tasks.
- What BEAM changes
- The VM preemptively schedules BEAM processes, each with its own mailbox.
- False friend
- Python has multiple runtimes and free-threaded builds; do not turn the GIL into a timeless rule.
JavaScript
- Familiar starting point
- An event loop, Promises, and Workers.
- What BEAM changes
- Many BEAM processes wait on their own mailboxes instead of sharing one application callback queue.
- False friend
- JavaScript Workers also isolate work; JavaScript is not simply ‘always single-threaded.’
Send the counter three letters
Send “add 1,” “add 2,” and “add 3,” then read the total. Besides finding 6, identify who owns the state.
- 01
Send
{:add, 1},{:add, 2}, and{:add, 3}to the counter in order - 02
Send
{:read, self()}with your PID so the counter knows the return address - 03
Use
Process.info(pid, :message_queue_len)to see how many messages remain in its inbox
# Send three addition messages; each call to send returns at once
send(pid, {:add, 1})
send(pid, {:add, 2})
send(pid, {:add, 3})
# Include your PID when asking for the current total
send(pid, {:read, self()})
# Wait for the reply; return timeout after one second
receive do
{:total, total} -> total
after
1_000 -> :timeout
end
# Check how many messages remain unhandled
Process.info(pid, :message_queue_len)- You eventually receive
{:total, 6} - After the read completes,
message_queue_lenusually returns to 0 - Other processes can send messages, but cannot reach in and directly change the number owned by the counter
Remove the {:read, caller} branch, then send a read message. The process will not crash. That message will remain in the mailbox.
This small process can update a count in message order while owning its own state.
This does not show that every message design is free from races, or that every mailbox will stay small forever.
Key ideas in this code
Lightweight process
An independent unit of work scheduled by BEAM. It is not a full operating-system process, so a program can create many of them.
mailbox
A process's own inbox. Messages wait here until the process looks for a pattern it can handle now.
reduction
A unit BEAM uses to estimate work. After one process has done some work, the scheduler gives other processes a chance.
Design patterns used here
Actor Model
Let one process own the state and cooperate with the outside through messages.
Single Writer
Let one owner serialize updates instead of sharing mutable state.
Message Filter
Use explicit message patterns to define what is handled and observe what is not.
What usually happens when a process mailbox contains a message that does not match any current receive pattern?
Draw a message map for three stations
Draw sensor, collector, and dashboard. Name each message and mark its sender and receiver.
Hint 1Take the first step
Do not name every message data. Say whether it carries a temperature, a query, or a reply.
Hint 2Make it a little smaller
If a message needs a reply, include a return PID or a reference.
Hint 3You are close
Ask which messages could pile up while the dashboard is not working.
Hint 4Work backward from the finish
Choose one success signal and write the smallest test for it. If the computer cannot show the result, rewrite the signal as something you can truly observe.
Every message shows who sends it and who receives it
Every piece of state has one clear owner
The map marks at least one mailbox that could grow
Remember three things
- 1
Each BEAM process owns its state and works with its partners through messages.
- 2
A message loop often carries the state for the next round in recursive arguments.
- 3
Messages that do not match yet remain in the mailbox, so mailbox length is worth watching.