skip to content

questions

5

Describe the actor model of concurrency: what an actor consists of, and why processing one message at a time removes the need for locks around an actor's state.

level: juniorimportance: must knowfreq 50%

answer

  1. private state + mailbox + behaviour
  2. one message at a time = mutual exclusion by construction
  3. only three actions: send, create, change behaviour
  4. no shared memory, asynchronous send, never block a handler
  5. millions of actors multiplexed onto a small pool

basics

~20 s

An actor is an isolated unit with private state, a mailbox, and behaviour. Actors never share memory; they only send each other asynchronous messages. Each actor processes its mailbox strictly one message at a time, so its state is never touched concurrently and needs no lock.

solid answer

~60 s

An actor has three parts: **private state** no other actor can reach, a **mailbox** (an incoming message queue), and **behaviour** — the code that handles one message. Actors communicate only by sending asynchronous messages: send is fire-and-forget, the sender does not block or wait for a result. The key invariant is **single-threaded message processing**: an actor handles exactly one message to completion before starting the next. So although thousands of actors run concurrently across a small pool of threads, any individual actor's state has only one thread of control touching it. Mutual exclusion is structural rather than something you remember to write, so there are no locks to forget, no lock ordering to get wrong, and no lock-based deadlock inside an actor. In handling a message an actor may do three things: send messages to actors it knows, create new actors, and change its own behaviour/state for the next message. The cardinal sin is blocking inside a handler — the actor cannot process anything else while blocked, and it occupies a pooled thread.

code

text · 8 lines
text
actor Counter:
  state count = 0                 // reachable only here

  on message Increment(n):
     count = count + n            // no lock: one message at a time

  on message GetCount(replyTo):
     send replyTo, Count(count)   // asynchronous, no return value

go deeper

for a junior

Give the three parts and the one-message-at-a-time rule, and conclude that private state plus serialized processing means no locks.

for a middle

Add the scheduling picture — many actors multiplexed over a small pool — and why blocking inside a handler is harmful; mention request/reply being modelled as two messages.

for a senior

Discuss what the model trades away: per-actor serialization as a throughput ceiling, lost stack traces, and hazards moving to delivery guarantees and mailbox growth.

for a principal

Position actors against alternatives (channels, stateless workers plus a database) and discuss when entity-per-actor modelling with sharding and supervision is worth its operational complexity.

## The model The actor model is a concurrency model in which the unit of computation is an **actor**: an entity with state that nobody else can touch, an address other actors can send to, and a mailbox where those messages queue up. There is no shared memory between actors, no shared mutable objects, and therefore nothing to lock. An actor consists of: - **Private state** — variables owned exclusively by this actor. Not reachable by reference from outside; not guarded by a lock because it does not need one. - **A mailbox** — a queue of incoming messages, filled asynchronously by any sender. - **Behaviour** — a handler that processes one message and may change what the handler does next. In response to a single message an actor can do exactly three things (the classic formulation): **send** a finite number of messages to addresses it knows, **create** a finite number of new actors, and **designate** the behaviour it will use for the next message. Everything an actor system does is composed of those primitives. ## Why no locks are needed The defining rule is that an actor processes **one message at a time, to completion**. The runtime guarantees it never runs a given actor's handler on two threads simultaneously. Since the state is reachable only through the handler, and the handler is effectively single-threaded, every classic shared-state hazard inside the actor disappears: no data race on the state, no torn read of a two-field invariant, no need to reason about which lock protects which field. This is mutual exclusion by construction. It is worth stating the contrast: with threads and locks, correctness depends on every programmer remembering to take the right lock in the right order every time; with actors, the runtime enforces serialization and there is nothing to forget. The failure modes move elsewhere (overflow, ordering, protocol errors) but the classic race is designed out. ## Concurrency without a thread per actor Actors are cheap because they are not threads. A runtime multiplexes millions of actors over a pool sized near the core count: when an actor has messages, the scheduler picks it up, runs the handler for one message (or a small batch), then releases the thread. An idle actor consumes memory for state and mailbox, and nothing else. That model has a hard requirement: **handlers must not block**. If a handler waits on a synchronous network call, a lock, or a sleep, it holds a pool thread while doing nothing and the actor's own mailbox stalls behind it. Real systems either forbid blocking in handlers or isolate blocking work onto a separate, dedicated pool. ## What the model gives you beyond lock-freedom - **Location transparency.** Since interaction is only by message to an address, the recipient can be in-process or on another machine. That is what makes actor runtimes a natural distribution fabric — with the caveat that remote sending has failure modes local sending does not. - **Encapsulated failure.** An actor that crashes takes its own state down, not the process; a supervisor decides what to do next. - **Natural modelling.** Anything with identity and state over time — a connection, a game entity, a shopping cart, a device — maps directly onto an actor. ## Where actors are not magic Serialization is per actor, so a single actor is a single-threaded bottleneck: throughput on one entity is capped by one core, and hot entities must be sharded across many actors. Asynchronous messaging also loses the call stack, so 'who caused this' becomes a tracing problem rather than a stack trace. And the ordering and delivery guarantees are weaker than a method call — a topic in its own right. ## Interview delivery Name the three parts (private state, mailbox, behaviour), state the one-message-at-a-time rule and derive lock-freedom from it, mention that actors are multiplexed over a small thread pool so blocking in a handler is the cardinal sin, and close with a one-line note that the hazards move to message ordering, delivery and mailbox growth rather than vanishing.

  • If actors never share memory, how does one actor get a result from another?
    By protocol, not by return value. The requester includes its own address in the message, and the responder sends a reply message back; the requester correlates it with the original request and applies a timeout, since no reply may ever arrive. Many runtimes wrap this as an 'ask' that yields a future, but underneath it is still two one-way messages.
  • What happens if a message handler performs a blocking network call?
    The actor cannot process any other message until it returns, so its mailbox backs up, and because actors share a small thread pool it also removes a worker thread from the whole system. Enough blocked handlers starve unrelated actors. The remedy is to make the call asynchronous, or to run blocking work on a separate dedicated pool and deliver the result back as a message.

An actor is a clerk in a private office with a single in-tray. Nobody may reach into the office; you slide a request into the tray. The clerk handles one slip at a time, so the files on the desk are never contested.

saying these in an interview costs you the question

  • Believing every actor has its own OS thread
  • Assuming send blocks until the recipient processes the message, like a method call
  • Sharing a mutable object by putting a reference to it in a message and continuing to modify it
  • Thinking actors eliminate all concurrency bugs rather than trading them for ordering, delivery and overflow issues
  • Doing blocking I/O or long computation inside a handler

context

open as a page

What message ordering and delivery guarantees do actor systems typically provide, what do they explicitly not provide, and how does that change the way you write message handlers?

level: middleimportance: must knowfreq 42%

basics

~20 s

Typically at-most-once delivery, with FIFO order preserved only per sender-receiver pair. There is no global ordering, no causal ordering through intermediaries, and no guaranteed delivery. So handlers must tolerate lost, delayed and interleaved messages, and be idempotent if you add retries.

open as a page

An actor receives messages faster than it can process them and its mailbox keeps growing. What strategies exist for handling mailbox overflow, and how do you choose between them?

level: seniorimportance: should knowfreq 34%

basics

~20 s

Options: unbounded mailbox (risks memory exhaustion), bounded with drop-newest, drop-oldest or priority-based dropping, bounded with rejection back to the sender, or credit-based flow control where senders only send what the consumer has requested. Choose by whether the messages are droppable, and fix the throughput mismatch as well.

open as a page

Explain supervision trees and the 'let it crash' philosophy in actor systems: who handles a failed actor, what options a supervisor has, and what this buys compared with catching every exception in place.

level: seniorimportance: should knowfreq 38%

basics

~20 s

Each actor has a parent that supervises it. On failure the child stops and its parent decides: restart it to a known-good state, resume it, stop it, or escalate to its own parent. Recovery lives in the tree, not scattered through handlers, so failure is isolated and state cannot stay corrupt.

open as a page

Your team is deciding whether to build a stateful system on the actor model rather than stateless services over a shared database. What do actors give you, where does the model break down, and how would you decide?

level: principalimportance: nice to knowfreq 28%

basics

~20 s

Actors fit entity-shaped, stateful, high-touch domains: isolated state, serialized per-entity access, supervision, natural sharding. They break down on cross-entity transactions, hot keys (one actor equals one core), lost stack traces and harder debugging, weak delivery guarantees, and state that must survive restarts. Decide by whether per-entity ownership is the dominant access pattern.

open as a page