Skip to main content

Tell vs Ask

Use Tell when you don’t need a reply: logging, notifications, commands that trigger side effects. Use Ask when you need a result: queries, computations, or any flow that depends on the response.

Message types (v4)

From v4.0.0, all message-passing APIs accept any. You can send:
  • Plain Go structs (with CBOR serialization when remote)
  • Protocol buffer messages (default ProtoSerializer)
  • Any type registered with the serializer
The framework handles serialization when the target is on another node. Locally, messages are passed by reference without serialization.

Keep messages pure

Treat every message as a pure data struct: fields should carry only values the receiver needs to process the message (IDs, payloads, commands, query parameters). Prefer immutable messages: do not mutate a message after it has been sent, and do not share mutable references between actors through message fields.
Do not embed concurrency primitives in messages to collect a reply: especially chan fields, mutexes, wait groups, or callbacks. That couples the sender’s goroutine to the actor’s mailbox, breaks the actor model, and can cause races, deadlocks, or unpredictable behavior when messages are stashed, forwarded, retried, or sent remotely (channels and function values do not serialize).
When you need a response, use Ask and reply with Response, or send a separate reply message with Tell. See Tell vs Ask and ReceiveContext messaging methods.

Message ordering

Messages between a specific sender-receiver pair are delivered in the order they were sent (FIFO). This follows from the mailbox being a FIFO queue and the single-threaded processing guarantee per actor.

Sender context

In Receive, you can access the sender via ctx.Sender(). It returns a *PID or nil (e.g., for scheduled messages or when the sender is unknown). Use ctx.Sender().Path() when you need the sender’s address for a reply.

ReceiveContext messaging methods

Inside Receive, the ReceiveContext provides these messaging operations: Use Sender() to get the sender’s PID when replying. Use ActorSystem().ActorOf(ctx, name) to resolve an actor by name before sending.

No shared state

Actors never share memory. All communication is through pure, preferably immutable message structs: not shared channels, mutable bags of state, or callbacks stuffed into the payload. That discipline eliminates whole classes of concurrency bugs and keeps behavior predictable under remoting, supervision, and mailbox scheduling.