100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
Programming

Processes and Message Passing

Learn how Erlang's lightweight processes communicate exclusively through asynchronous message passing, forming the foundation of concurrent, fault-tolerant systems.

Concurrency & OTPBeginner8 min readJul 10, 2026
Analogies

Erlang Processes: Lightweight, Isolated Units of Computation

An Erlang process is not an operating system thread — it is a lightweight, independently scheduled unit of computation managed entirely by the BEAM virtual machine. Each process starts with a tiny memory footprint (a few hundred words), has its own private heap and stack, and shares absolutely no memory with any other process. Because process creation and context switching are handled by BEAM's own scheduler rather than the OS kernel, a single Erlang node can comfortably run hundreds of thousands to millions of concurrent processes, each isolated from the others by design.

🏏

Cricket analogy: Think of each Erlang process like a fielder positioned independently around the ground during a Virat Kohli-led innings — each fielder (process) watches their own zone, holds no shared equipment, and only reacts when the captain sends a specific instruction via hand signals (a message).

Sending and Receiving Messages

Processes communicate exclusively by sending messages with the ! operator, written as Pid ! Message. Sending is asynchronous: the sender copies the message into the target process's mailbox and continues executing immediately, without waiting for the message to be read or acknowledged. On the receiving side, a receive block pattern-matches against the clauses it defines, scanning the mailbox for the first message that matches any clause — it does not have to be the oldest message in the queue.

🏏

Cricket analogy: When a captain like Rohit Sharma signals a field change from the boundary, he doesn't wait to confirm the fielder saw it before moving on to the next instruction — this is exactly how the ! operator fires a message asynchronously without blocking the sender.

erlang
-module(ping_pong).
-export([start/0, loop/0]).

start() ->
    Pid = spawn(fun loop/0),
    Pid ! {self(), hello},
    receive
        {Pid, Reply} ->
            io:format("Got reply: ~p~n", [Reply])
    after 5000 ->
        io:format("Timed out waiting for reply~n")
    end.

loop() ->
    receive
        {From, hello} ->
            From ! {self(), world},
            loop();
        stop ->
            ok
    end.

Process Isolation and the Mailbox

Each process's mailbox is a private, per-process FIFO queue that only that process can read from; no other process can inspect or drain it directly. Combined with the fact that each process has its own heap that is garbage collected independently of every other process, this means a bug that corrupts one process's data — or a crash that terminates it — has no way to reach into and damage another process's memory.

🏏

Cricket analogy: Each player's individual locker in the dressing room, holding only their own gear, means if one player's kit bag gets soaked in the rain, no other player's equipment is affected — just as a process's private heap crashing doesn't corrupt another process's memory.

Because processes never share memory, a crash inside one process's code — even a serious one like a failed pattern match — cannot corrupt the heap or state of any other process. This isolation is the foundation Erlang's supervisor trees are built on.

Selective Receive, Timeouts, and Mailbox Growth

Because receive performs selective, pattern-based matching rather than simply consuming the mailbox's head, a process can choose to wait for a specific kind of message while ignoring others, which remain queued for a later receive to pick up. Adding an after Timeout -> ... end clause lets a receive give up waiting after a specified number of milliseconds rather than blocking forever, which is essential when a reply might never arrive.

🏏

Cricket analogy: A wicketkeeper listening only for the bowler's specific appeal call while ignoring unrelated crowd noise resembles selective receive scanning the mailbox for a matching pattern and leaving everything else queued for later.

If a process repeatedly calls receive with a narrow pattern (e.g., only matching {tag, Data}) while other kinds of messages keep arriving, those unmatched messages pile up in the mailbox indefinitely. A growing, unprocessed mailbox increases memory usage and can slow down pattern matching, since receive must scan past every unmatched message each time.

  • Erlang processes are lightweight, isolated, and run on the BEAM VM's own scheduler, not OS threads.
  • Processes share no memory; all communication happens via asynchronous message passing with !.
  • receive performs selective pattern matching against the process's private mailbox.
  • Sending a message with ! never blocks the sender, regardless of whether it's ever received.
  • Each process has its own heap, so a crash in one process cannot corrupt another's memory.
  • Unmatched messages remain in the mailbox and can cause it to grow if never handled.
  • The after clause in receive sets a timeout for waiting on a message.

Practice what you learned

Was this page helpful?

Topics covered

#Programming#ErlangStudyNotes#ProcessesAndMessagePassing#Processes#Message#Passing#Erlang#StudyNotes#SkillVeris#ExamPrep