Actor System

Message passing with mailboxes and supervisors

Design a small actor system: actors that own their state and talk only by messages, a mailbox per actor, a shared thread pool that runs each actor one message at a time, and supervisors that restart or stop actors that fail.

Overview

An actor is an object that owns some state and a mailbox. Nobody calls its methods. Other code sends it a message, the message waits in the mailbox, and the actor handles its messages one at a time. Because only one message is ever inside an actor at once, the actor's own state needs no lock. The idea goes back to Carl Hewitt, Peter Bishop and Richard Steiger's 1973 paper on the actor formalism; Erlang/OTP, Akka and Microsoft Orleans are well-known systems built on it.

The interview version asks you to build the runtime, not just use one. Four parts carry the design. The reference (ActorRef) is the only handle callers get, so nobody can reach into an actor's fields. The mailbox and its scheduling flag guarantee one message at a time. A shared thread pool runs many actors on a few threads. A supervisor decides what happens when an actor's handler throws: resume, restart with fresh state, or stop.

A strong answer states the one-at-a-time guarantee and shows exactly which lines provide it, explains why one thread per actor does not scale, and treats failure as a normal event with a policy, not as a stack trace in a log.

Scoping the problem

Ask these questions before you write code; each answer changes a class.

  • One process, or actors on many machines? — Remote actors need serialization, addressing and lost-message handling — One process; remote is a follow-up
  • How many actors, and how many threads? — Thousands of actors cannot each own a thread — Many actors on a small pool
  • Fire-and-forget only, or request and reply too? — Reply needs a reply address or a future — Both: tell and ask
  • What happens when a handler throws? — Supervision policy and its limits — Restart with fresh state, at most N times, then stop
  • What happens to messages sent to a stopped actor? — They must go somewhere visible — Dead letters, for debugging
  • Must messages from one sender arrive in order? — Decides the mailbox type — Yes, FIFO per mailbox

One message at a time

The whole design rests on one rule: at most one worker thread runs a given actor at any moment. The mailbox is a queue behind a small lock, plus a flag, scheduled. A send appends the message; if the flag was off, the sender turns it on and submits the actor to the pool. A worker then handles up to throughput messages. When the mailbox is empty it turns the flag off; if messages remain after its turn, it resubmits the actor, so one busy actor cannot keep a thread forever.

The lock in the cell guards only the queue and the flag, for a few instructions. The actor's state (self.n in the demo counter) has no lock at all. The demo starts 8 threads that each send 1,000 increments and then asks for the total: exactly 8,000.

Tell and ask

  • tell(message, sender) — nothing — no — the normal case; a reply, if any, arrives as a new message to the sender
  • ask(message, timeout) — the reply, through a Future — yes, up to the timeout — at the edge of the system: a web handler or a test asking an actor

A handler that raises while answering an ask fails the Future, so the caller sees the exception instead of waiting for the timeout.

Failure and supervision

A handler that raises does not crash the thread or the system. The worker catches the error and asks the actor's supervisor, its parent, for a directive. Without a parent the system restarts the actor.

  • RESUME — kept — kept — the error was about one bad message, and the state is still valid
  • RESTART — reset to a fresh instance — kept — the state may be corrupt; start clean
  • STOP — gone — moved to dead letters — the actor cannot recover

What you will be able to do

  • Explain why an actor's state needs no lock and point to the lines that guarantee it.
  • Build a mailbox with a scheduled flag that runs many actors on a small thread pool.
  • Implement tell, ask with a Future, and dead letters.
  • Design supervision with resume, restart and stop, and a restart limit.
  • Answer the follow-ups: blocking handlers, ordering and delivery guarantees, and actors on many machines.

Functional requirements

  • spawn(factory, name, parent) creates an actor and returns its ActorRef; names are unique
  • tell(message, sender) queues a message and returns at once
  • ask(message, timeout) returns the reply, or raises the handler's error, or times out
  • Each actor handles its messages one at a time, in the order they entered its mailbox
  • An actor can spawn children; a parent decides RESUME, RESTART or STOP when a child fails
  • After max_restarts restarts an actor is stopped; messages to a stopped actor go to dead letters and fail any ask

Non-functional requirements

  • Many actors share a small ThreadPoolExecutor; no thread per actor
  • An actor gives up its thread after throughput messages, so a busy actor cannot starve others
  • Actor state needs no locks; the cell lock guards only the mailbox and the scheduled flag, for a few instructions
  • A failing handler never kills a worker thread or the system

Interview questions

  • Why does the counter not need a lock, when eight threads send to it?
  • Why a thread pool instead of one thread per actor?
  • How would you add one-for-all supervision?
  • Can an actor call ask() on another actor inside receive?
  • What delivery and ordering guarantees does your system give?
  • How would the design change for actors on many machines?

Sign up free to read the full topic

More Low-Level Design topics

Start free on Cariara · Coding problems · System design studio