Skip to content
matthewcm.dev

    ↑ ↓ move · ↵ open · tab scopeopen graph →

    Message Passing

    "Don't communicate by sharing memory; share memory by communicating." Independent processes exchanging messages over channels, from Go and actors to queues and WebSockets.

    Growingtended planted 2 min read
    On this page

    There are two broad ways for concurrent things to cooperate. They can share memory and protect it with locks, or they can send each other messages and keep their state private. The Go proverb puts it best: don’t communicate by sharing memory; share memory by communicating.

    Why messages

    Shared memory with locks is fast and familiar, but it invites race conditions, deadlocks and code where correctness depends on every caller remembering the rules. With message passing, each process owns its state outright. The only way to affect it is to send it a message, which makes the interactions explicit — and much easier to reason about, test and distribute across machines.

    Design choices for a channel

    • Synchronous or asynchronous. A synchronous send blocks until the receiver takes the message (a rendezvous). An asynchronous one drops it into a buffer and carries on — faster, but you now need to think about full buffers and backpressure.
    • Uni- or bi-directional, one-to-one or one-to-many.
    • Ordering and delivery guarantees. Within one machine these are easy. Across a network they’re the whole game — see Message Delivery Semantics.

    Where it shows up

    • Go goroutines and channels.
    • Actors (Erlang/Elixir, Akka): every actor has a mailbox and processes one message at a time. Supervisors restart actors that crash — “let it crash” as a reliability strategy.
    • Session types go a step further and check at compile time that both sides follow the agreed protocol. I met these at university and still find them a beautiful idea.
    • JavaScript itself: the event loop, postMessage to web workers, and async/await are all cooperative message handling.
    • Distributed systems: queues, event streams and real-time channels such as WebSockets and Server-Sent Events (SSEs) are message passing between machines.

    A small example

    Two web workers crunching data for a dashboard could share a buffer guarded by locks (SharedArrayBuffer with Atomics) — fiddly and easy to get wrong. Or each worker can own its slice and post results back to the main thread, which merges them. The second version is slower by a copy and simpler by a mile.

    In practice

    Real-time UIs are message-passing systems wearing a costume. A socket delivers messages; the UI’s job is to apply them to a state it owns, in order, without letting every message trigger a re-render. Thinking of the frontend as “a process with a mailbox” is what led me to the single-owner pattern in Zustand Transient Updates.

    • #concurrency
    • #message-passing
    • #actors
    • #channels

    ↪ Links to (4)