# Processes and Message Passing — Elixir & Phoenix

Source: https://www.geekswithgeeks.com/en/elixir-phoenix/o-processes

> Spawn processes, send and receive messages, and link or monitor them.

## The actor model on the BEAM

A BEAM **process** is an isolated unit of execution with its own heap and **mailbox**; it is not an OS thread, and creating one takes microseconds and a few kilobytes. **`spawn/1`** starts a process running a function and returns its **PID**. Processes communicate only by **messages**: `send(pid, message)` is asynchronous and copies the message into the receiver's mailbox, and **`receive`** pattern-matches messages from the mailbox, optionally with an `after` timeout. Because nothing is shared, there are no locks or data races. A process keeps **state** by calling itself recursively with new arguments in a receive loop. **Links** (`spawn_link`) tie two processes together so that if one crashes, the other receives an exit signal and normally crashes too, which supervisors use to detect failures. **Monitors** (`Process.monitor`) are one-way: the watcher receives a `:DOWN` message instead of crashing. Processes can be **registered** under a name. The BEAM schedules processes **preemptively**, so one busy process cannot freeze the others, which keeps latency predictable.

## Processes and mailboxes

Each process has a private mailbox; messages are copied, never shared.

![Three circles each with a small envelope tray, with envelopes moving between them along arrows.](assets/figures/elixir-phoenix/section-4-map.svg) — Figure 4.1 — Message passing between isolated processes.

## A stateful counter process

A recursive receive loop holds state; links and monitors report failures.

```elixir
defmodule Counter do
  def start(initial \\ 0), do: spawn(fn -> loop(initial) end)

  defp loop(count) do
    receive do
      {:increment, by} ->
        loop(count + by)                      # new state via recursion

      {:get, caller} ->
        send(caller, {:count, count})
        loop(count)

      :stop ->
        :ok                                   # returning ends the process
    end
  end
end

pid = Counter.start()
send(pid, {:increment, 5})
send(pid, {:increment, 2})
send(pid, {:get, self()})

receive do
  {:count, value} -> IO.puts("count = #{value}")    # count = 7
after
  1_000 -> IO.puts("no reply")
end

# monitoring: get a :DOWN message instead of crashing
worker = spawn(fn -> raise "boom" end)
ref = Process.monitor(worker)

receive do
  {:DOWN, ^ref, :process, ^worker, reason} -> IO.puts("worker died: #{inspect(reason)}")
end
```

## Always plan for unmatched messages

Messages that no receive clause matches stay in the mailbox forever, slowing every future receive. GenServer handles this for you; in hand-written loops, add a catch-all clause or use timeouts.

**Quiz:** How do BEAM processes share data?

- [ ] Through shared memory and locks
- [ ] Through global variables
- [x] They do not share memory; they communicate by copying messages into mailboxes
- [ ] Through files only

*Answer:* They do not share memory; they communicate by copying messages into mailboxes. Isolation plus message passing eliminates data races.
