Free — no signup required

Anatomy of an Actor

3 min read

The Three Components in Detail

Think of an actor like a person sitting in a private office with a locked door and an inbox slot on the door. You cannot walk in and rearrange their desk (no direct state access). You can only slide a note through the slot (send a message). They will read it when they are ready, act on it, and possibly slide notes under other people's doors in response.

1. The Mailbox

When you send a message to an actor, the message is placed in the actor's mailbox — a FIFO queue. The actor picks messages off the queue one at a time. If the actor is busy processing message #5, messages #6, #7, and #8 wait patiently. This is what makes the model safe: the actor's state is only ever touched by one piece of code at a time.

Sender ──► [msg1, msg2, msg3] ──► Actor processes msg1
                 ▲                      │
           mailbox queue           modifies state
                                        │
                                   sends reply

2. Private State

The actor's state is just regular variables — a counter, a map, a list, a database connection. The difference is that these variables are completely hidden. No getter, no setter, no shared reference. The only way to observe or change the state is to send the actor a message and wait for a reply.

# Pseudocode: Actor internal state
class WalletActor:
    state = {"balance": 500.00}  # PRIVATE — nobody else touches this

    def on_message(self, msg):
        if msg.type == "DEPOSIT":
            self.state["balance"] += msg.amount
            self.reply({"status": "ok", "new_balance": self.state["balance"]})
        elif msg.type == "GET_BALANCE":
            self.reply({"balance": self.state["balance"]})

3. Behavior (Message Handling)

The behavior is the switch statement (or pattern match) that decides what to do for each message type. Crucially, the behavior can also change — an actor can switch to a different behavior after processing a message. This is how actors model state machines. A ConnectionActor might have a disconnected behavior and a connected behavior, and switch between them based on messages received.

Fire and Forget: Asynchronous Messaging

This is the most important shift in thinking when moving to the Actor Model. In object-oriented programming, calling a method is synchronous — you call it, you block, you wait for the return value:

// OOP: Synchronous — caller blocks until result is returned
int balance = walletService.getBalance(userId);
// Nothing below this line runs until getBalance() returns

In the Actor Model, sending a message is asynchronous — you send it and immediately continue:

// Akka (Scala): Asynchronous — fire and forget
walletActor ! GetBalance(userId)
// This line runs immediately, before walletActor processes the message

If you need a response, you send a message that includes a reply-to address (a reference to yourself), and the receiving actor sends a message back when it is ready. This is called the ask pattern and it returns a Future or Promise that resolves when the reply arrives.

The implication: you can never assume a message was processed just because you sent it. This is not a bug — it is the model. It forces you to design systems that are explicitly resilient to partial failures.

This is one of 18 chapters

Every chapter is free — Kubernetes, Terraform, SRE, distributed systems, and more — with fast daily review built in.

Sign up free