skip to content

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%

answer

  1. at-most-once by default
  2. FIFO per sender-receiver pair only
  3. no global, cross-sender or transitive causal order
  4. at-least-once = retry + ack; idempotency for effectively-once
  5. business-level ack, not transport ack

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.

solid answer

~50 s

The usual baseline is **at-most-once delivery**: a message is delivered once or not at all, and the runtime does not retry. Ordering is preserved **per sender-receiver pair** — if A sends m1 then m2 to B, B sees them in that order if it sees both — but nothing more. Specifically not guaranteed: any global order; ordering between different senders (B's mailbox interleaves A's and C's messages arbitrarily); causal order through a third party (A→B→C can arrive at C after A→C sent later); and delivery itself, especially across a network. That drives handler design. Assume messages can be lost, so anything that must happen gets an application-level acknowledgement and a retry, which upgrades you to **at-least-once** — and therefore requires **idempotent** handlers or de-duplication by message id. Assume unrelated messages interleave, so state machines must handle 'unexpected message in this state' explicitly rather than assuming a sequence. And never encode a protocol that depends on two different senders' messages arriving in a particular order.

code

text · 5 lines
text
A -> B: x        (then B forwards x to C)
A -> C: y        (sent after x)

C's mailbox may contain:  y, then x
Guaranteed only: two messages A->C keep their relative order.

go deeper

for a junior

Recall that delivery is at-most-once and ordering is preserved only between one sender and one receiver, so handlers must tolerate loss and unexpected order.

for a middle

Enumerate the non-guarantees precisely, explain the at-least-once + idempotency ladder, and name the concrete handler practices — correlation ids, timeouts, explicit state machines.

for a senior

Explain why exactly-once delivery is impossible while exactly-once processing is not, insist on business-level acks, and design the ordering solution (single owner per entity key, sequence numbers).

for a principal

Decide per message class which reliability tier is worth its cost, and address the system-wide consequences: durable outbox, de-duplication storage, traceability across asynchronous hops, and behaviour after restarts.

## The default guarantees Actor runtimes deliberately promise little, because promising more is expensive and, across a network, impossible to promise absolutely. **Delivery: at-most-once.** The runtime attempts delivery and does not retry. A message may be lost — because the target actor was stopped, because the mailbox rejected it, because the remote node was unreachable, or because the process died. In-process delivery rarely loses messages, which is exactly why teams build in an assumption they only discover to be false when they distribute. **Ordering: FIFO per sender-receiver pair.** If actor A sends m1 then m2 to actor B, and both arrive, B processes m1 first. This is the strongest ordering property most runtimes offer, and it is enough for many protocols. ## What is explicitly not guaranteed - **No global ordering.** There is no system-wide sequence of messages; each mailbox has its own arrival order. - **No ordering across senders.** If A and C both send to B, their messages interleave in any way. Wall-clock 'A sent first' means nothing. - **No causal ordering through intermediaries.** A sends x to B, B forwards to C; separately A sends y directly to C. C may see y before x even though x was caused first. Ordering is pairwise and does not compose transitively. - **No delivery guarantee.** 'Guaranteed delivery' does not exist end to end; the two-generals result says no protocol can make both sides certain. What you can build is retry-until-acknowledged, which is at-least-once, plus de-duplication for effectively-once processing. - **No ordering after a restart.** If a supervisor restarts an actor, in-flight messages may be dropped and the pairwise sequence broken. ## The three delivery semantics, and who pays for each - **At-most-once:** send and forget. Cheapest, no state. Loses work on failure. Fine for telemetry, cache invalidation hints, UI updates. - **At-least-once:** sender keeps the message until an application-level ack, retrying on timeout. Requires sender-side storage and duplicate handling downstream. This is the workhorse for anything that must not be lost. - **Exactly-once delivery:** not achievable across a failure boundary. What is achievable is *exactly-once processing* — at-least-once delivery plus idempotent handlers or de-duplication keyed by a message id, so that repeats have no additional effect. Crucially, the acknowledgement must be at the **business level** — 'I have durably recorded this order' — not at the transport level. A transport ack tells you bytes arrived, not that the work survived a crash. ## How this changes handler code 1. **Make handlers idempotent** wherever retries exist. Key by a stable request id and record processed ids, or express the effect as a set/assignment rather than an increment. 2. **Write explicit state machines.** Every actor state should define what happens for every message type it might receive, including ones that arrive 'too early' or 'too late' — stash them, reply with an error, or discard by policy. Never assume 'this can only arrive after that'. 3. **Always time out request/reply.** No reply is a normal outcome, not an exception, since neither delivery nor a response is guaranteed. 4. **Do not rely on cross-sender order.** If two producers' events must be ordered, funnel them through one actor which assigns a sequence, or attach a version/logical timestamp and reconcile. 5. **Carry correlation identifiers** so a reply can be matched, duplicates detected and a distributed trace assembled — the call stack is gone. 6. **Design for redelivery after restart.** If the actor is restarted by a supervisor, the sender may retry; the actor's recovery path must not double-apply effects. ## Why the defaults are set this way Stronger guarantees cost latency and durability work: making every message reliable means persisting it before sending, acknowledging, retrying and de-duplicating — an order of magnitude more machinery than a mailbox enqueue. Actor runtimes choose the cheap default and let you opt into reliability exactly where the business needs it, message by message. That is a deliberate design, and interviewers like candidates who can say which messages in their system deserve the expensive treatment and which do not. ## Interview delivery State at-most-once plus per-pair FIFO, enumerate the three non-guarantees (no global order, no cross-sender order, no transitive causal order), explain the at-least-once + idempotency ladder including why exactly-once delivery is impossible but exactly-once processing is not, and finish with the concrete handler practices: idempotency keys, explicit state machines, timeouts, correlation ids.

  • Why can't an actor system offer exactly-once delivery?
    Because a sender can never distinguish 'the message was lost' from 'the message arrived but the acknowledgement was lost'. Retrying risks a duplicate, not retrying risks loss — the two-generals problem. What you can achieve is exactly-once *processing*: retry until acknowledged, and make the receiver idempotent or de-duplicate by message id so repeats have no extra effect.
  • Two producers send events about the same entity and the order matters. How do you enforce it?
    Do not rely on arrival order, because ordering holds only per sender-receiver pair. Route both producers' events through a single actor that assigns a sequence number, or attach a version or logical timestamp at the source and have the consumer reject or reorder out-of-sequence updates. Partitioning by entity key so one actor owns each entity is the usual actor-shaped answer.
  • Does a supervisor restart preserve the actor's pending messages?
    Not reliably. Depending on the runtime and the strategy, the mailbox may be preserved, drained or dropped, and the message that caused the failure is typically discarded so it cannot poison the actor repeatedly. Senders that need the work done must retry, which means the restarted actor's recovery path has to be idempotent.

saying these in an interview costs you the question

  • Assuming messages arrive in global send order
  • Believing delivery is guaranteed because it never fails in local testing
  • Claiming exactly-once delivery is achievable with enough retries
  • Adding retries without making the handler idempotent
  • Using a transport-level acknowledgement as proof the work is durable

context