Articles
Erlang Interview Questions: Processes, Mailboxes, and Distributed Failure
Explain Erlang processes, selective receive, links, monitors, remote-call timeouts, and distributed failure through a practical inventory case.
An Erlang interview question can begin with Pid ! Message and end with a network partition. The hard part is explaining what the receiving process has actually observed. A send expression, a receive clause, a monitor's 'DOWN' message, and a remote gen_server:call timeout have different meanings. None by itself proves that a business side effect happened exactly once. A strong answer traces the mailbox and the failure boundary separately.
Suppose node A asks a process on node B to reserve inventory. The request reaches B, B commits a reservation, and the connection drops before A sees a reply. A receives a timeout or node-down signal and considers retrying. If it blindly sends the same logical reservation again, it may reserve twice. To reason through this, distinguish process communication from durable business identity. Practice the delivery-semantics part with Explain Kafka and Message-Queue Delivery Semantics.
Evidence boundary: Official facts below come from current Erlang/OTP documentation checked September 2026. Candidate reports are not used to predict an employer's exact prompt. Inference includes the inventory scenario, recommended idempotency key, and suggested observability tests; adapt them to your system's contracts.
What is an Erlang process responsible for?
An Erlang process executes independently with its own mailbox and state. Processes communicate by sending messages, rather than sharing a normal mutable heap. The Erlang process reference documents links and monitors as separate failure-signal mechanisms. These properties make it easier to isolate a process failure, but isolation is not a business recovery plan. If the process owns only transient state, it may be cheap to restart; if it has called an external payment or inventory service, that effect must be accounted for separately.
When you send to a PID, think of the operation as a message transfer attempt to that process, not an acknowledgement that its application handler ran. A sender can then wait for a reply, monitor the target, or continue asynchronously, depending on the contract. The more important the result, the more explicit the acknowledgement and durable operation ID need to be. A protocol with only a send and no correlation reference makes it hard to distinguish a delayed reply from a reply to a later attempt.
| Mechanism | What it offers | What it does not prove |
|---|---|---|
Pid ! Message | Sends a message to a process identifier | The receiver handled or committed it |
receive | Chooses a matching mailbox message | Unmatched messages vanished |
| Link | Exit signal between linked processes | The business operation was rolled back |
| Monitor | A 'DOWN' observation for a target | Exactly when external work stopped |
gen_server:call | Request/reply protocol with timeout | No effect occurred if the caller times out |
The word “lightweight” is often used for BEAM processes, but in an interview it is more useful to explain ownership. Which process owns the mailbox? Which process maintains state? Which actor should be notified if it exits? Which data must outlive that PID? Those answers determine whether a process tree supports the application's correctness requirements.
How does selective receive affect a mailbox?
Erlang's receive expression searches a process's message queue for the first message matching one of its clauses. Messages that do not match remain in the queue for later receives. For example, if the mailbox contains {metric, 1}, {reply, Ref, ok}, and {metric, 2}, a receive looking for {reply, Ref, Result} can select the second message while the metric messages remain. This is not simply “pop the next item” as in a FIFO work queue.
Selective receive is powerful for request/reply protocols, but it has a cost if many unmatched messages accumulate. A process that only matches one rare shape may scan a growing mailbox repeatedly. In an interview, explain both correctness and operations: preserve unrelated messages, use references to correlate replies, and monitor mailbox length and processing latency when input rates grow. Do not solve every unmatched message by adding a wildcard clause that silently discards valid future work.
Consider a small mailbox trace:
Mailbox: [old telemetry, reply(ref-7), new telemetry]
receive reply(ref-7) -> selects the middle message
Remaining: [old telemetry, new telemetry]
Now introduce a stale reply with ref-6 before the desired reply. A receive matching only ref-7 should not treat ref-6 as the current result. References help keep concurrent requests distinct. The exact patterns and timeout handling are protocol choices, but the key interview point is that the mailbox's contents survive an unmatched scan until handled or the process exits.
How do links and monitors differ?
A link is bidirectional. Erlang's process reference says that when one linked process terminates, an exit signal is sent to the other. Links are a foundation for failure propagation in process trees. A monitor is unidirectional: the monitoring process receives a 'DOWN' message containing a reference, monitored target, and reason when the target terminates. Multiple monitors of the same target are independent.
Use the distinction to answer the design question. If two processes' lifecycles should be coupled, a link may be appropriate, usually under a deliberate OTP supervision structure. If a caller merely wants to observe whether a worker dies without having its own lifecycle tied to that worker, a monitor is often the clearer tool. A trap_exit decision changes how some exit signals are handled; do not casually claim every linked exit always kills the peer in every configuration.
For a remote worker, the monitor also raises an epistemic limit. A node disconnect can produce an observation that communication is unavailable; it cannot tell you whether a remote database transaction committed just before disconnection. Treat a node-down notification as a trigger for reconciliation, not proof that the remote operation never happened.
What does a gen_server:call timeout mean?
The current Erlang gen_server documentation describes a synchronous request that waits for a reply or times out. A timeout exits the calling process unless it handles the exit. It may be caused by a slow server, a blocked message path, or a distributed connection issue. It means the caller did not obtain a reply within the timeout; it does not imply the server did no work.
This is crucial in the inventory example. A may time out after B reserved the item. The next attempt must use the same logical reservation_id, query or reconcile the durable result, and avoid creating a second reservation. If B never received the first call, a retry with that same ID should still be able to make progress. Design the server's command as idempotent at the business boundary; do not try to make the network deliver a magical exactly-once message.
Version details can matter. The current gen_server docs note that starting in OTP 24, gen_server:call uses process aliases so late replies after a timeout do not arrive in the caller's mailbox as older guidance warned. If an interviewer cites an old stale-reply issue, specify the OTP version rather than repeating an obsolete blanket rule. The broader uncertain-effect problem remains even when the late-reply mailbox cleanup improved.
What changes across Erlang nodes?
Distributed Erlang consists of runtime nodes that can exchange messages and signals. The official distribution guide says PID-based message passing, links, and monitors can work across nodes, while registered names are local unless a node is specified. Node connections can be established as processes communicate, and a lost connection removes that node relationship. monitor_node can deliver {nodedown, Node} when a connection is lost.
Do not confuse transparent process messaging with transparent business consistency. A partition may prevent A from reaching B while B continues serving its own local work. A monitor can tell A that B's node is not reachable through that connection; it cannot prove the remote process is dead or its transaction rolled back. If two sides can both act during a partition, the application needs a single durable authority, lease/fencing scheme, or reconciliation rule appropriate to its data. The correct choice depends on consistency requirements, not on Erlang syntax alone.
The distribution guide also warns that default distributed-node traffic is clear text and that exposing an insecure node can be dangerous. A production answer should not treat the magic cookie as encryption or an internet-facing security boundary. Use appropriate transport security, network controls, and identity policies for the deployment. For an interview focused on failure, mention this succinctly rather than changing the whole answer into a security checklist.
Walk through the inventory partition
At time one, A sends reserve(order-42, item-9) to B with a stable reservation ID. At time two, B commits the reservation in durable storage. At time three, A and B lose connectivity before B's reply arrives. A observes a timeout or node-down condition. There are now at least two plausible worlds from A's point of view: B committed, or B did not. A cannot select one merely by reading the timeout.
The recovery protocol asks the durable authority for the reservation ID after connectivity returns. If it exists, return the prior result; if it does not, perform the reservation once under a unique constraint or transaction. If the durable store itself is partitioned, the system must define whether to reject writes, use a leader/fencing rule, or accept later conflict repair. Avoid claiming that process monitors solve this distributed data problem. They solve observation of process/connectivity events.
Test this with a controlled fault: pause the reply after B commits, break the connection, and retry the same reservation ID. Assert one durable reservation and a stable result to A. Run the complementary case where the request never reaches B; retry should eventually create one reservation. Add a concurrent duplicate request from a second caller to test the uniqueness guard. These are stronger than asserting only that a 'DOWN' message appeared.
How do you diagnose mailbox and distributed failures?
Collect process identity, node, mailbox length, input rate, time spent in receive handlers, timeout counts, and node-connectivity events. Correlate requests with unique references and business operation IDs, but do not log sensitive payloads indiscriminately. A large mailbox can mean the process is slow, the receive pattern is too selective, a producer is unbounded, or an upstream retry loop is flooding it. Each calls for a different fix.
For a node partition, correlate both sides' logs. One node's nodedown event and a caller timeout are not enough to establish what the remote side committed. Query durable records and downstream provider IDs. If the system has a supervisor, verify the right processes restarted, but also verify that pending business work was replayed or reconciled. Operational evidence should include both process health and user-visible correctness.
What follow-up answers avoid common traps?
“Does receive discard unmatched messages?” No. It selects a matching message; unmatched ones stay in the mailbox. But an ever-growing unmatched set is an operational problem.
“Is a monitor the same as a link?” No. A link is bidirectional exit-signal coupling; a monitor is a one-way observation with a reference and 'DOWN' message.
“Does a remote call timeout mean no reservation happened?” No. The reply may be lost after the durable effect. Reconcile by business ID before retrying.
“Are distributed Erlang messages automatically secure?” No. The current distribution guide warns about clear-text defaults and insecure node exposure; production design needs transport and network protection.