The System Design Template: Write to a Queue, Read from a Cache
The master system design template: stateless services, a queue on the write path, a cache on the read path, and how it solves Twitter and WhatsApp.
Most scalable services, and therefore most system design interview answers, share one shape. Learn it once and you have a strong first draft for almost any problem. The whole idea fits in two lines:
Write to a message queue. Read from a cache. The database is the source of truth, but neither path talks to it directly on the hot path.
The components
Stateless services
Both services keep no user state in memory, so they scale by adding machines behind a load balancer. See stateless vs stateful.
- Write service: accepts client requests that change data and forwards them to the message queue.
- Read service: answers read requests from the cache.
Database
Cold, durable storage and the source of truth. You normally don't read from it directly on the hot path, because it becomes slow when request volume is high. Which database to pick depends on the data and queries: see SQL vs NoSQL.
Message queue
A buffer between the services that write and the storage that holds data.
- Producers: the write services, which publish each data change to the queue.
- Consumers: workers that apply those changes to the database and the cache.
- Database updater: asynchronous workers that take jobs from the queue and write them to the database.
- Cache updater: asynchronous workers that take jobs from the queue and refresh the cache. (Often the same worker does both.)
Cache
In-memory storage, such as Redis, that makes reads fast and cheap. See caching.
The two dataflow paths
Nearly every application splits into read requests and write requests. They have completely different consequences: a read changes nothing, while a write mutates the database. So design the two paths separately.
Write path
The write service accepts the request, puts it on the message queue and returns immediately (for example with 202 Accepted). A worker then persists the change to the database and updates the cache. Because nothing slow happens while the user waits, each part of the system can work at its own pace and the user still gets a fast response.
Read path
For large applications with millions of daily users you almost always read from the cache, not the database. The database is permanent storage; background jobs keep copying fresh data from it, or from the write stream, into the cache. The database is touched only on a cache miss.
Why the queue is essential
The queue is what lets the system scale for writes. Producers put messages in; consumers take them out and process them asynchronously. It is needed for two reasons:
- Producers and consumers run at different speeds. A buffer absorbs the difference. The frontend can post comments far faster than the backend can write them to the database; a booking request arrives much faster than the booking service can confirm it with a third-party provider.
- Fault tolerance. If the write service called the database workers directly and that call failed, the request would be lost. With a queue, messages are stored durably, so if the workers are down the messages wait safely and are processed when the workers recover.
One database per service
Never expose a database directly to clients. Put a service in front of it and expose the data through an API. The service can then validate requests, enforce permissions, change the schema without breaking clients, and add caching. In a microservices system each service owns its own database. See database per service.
Using the template: Twitter
Twitter's timeline is the textbook case. Tweets and follow/unfollow actions go through write services into the queue; workers update the database and pre-build each user's timeline as an "inbox" in the cache. When a user opens the app, the timeline service just reads their inbox. See the full Twitter design and fan-out.
Using the template: WhatsApp
A chat app uses the template with one twist: users hold open connections. Each chat server keeps a map of which client is connected to it (client_a → connection_1). When the recipient is online, the message is pushed straight to their chat server and down their WebSocket. When they are offline, it waits in their inbox in the cache until they reconnect.
- Client A asks the load balancer for a chat server.
- The load balancer assigns one.
- Client A opens a WebSocket to that chat server and sends a message.
- The chat server gets a unique, time-ordered ID for the message from the ID generator.
- The chat server publishes the message to the queue.
- The queue confirms the message is stored.
- The chat server acknowledges to client A ("sent").
An MQ consumer then saves the message to the message database, puts it in the recipient's inbox in the cache, and, if client B is online, forwards it to client B's chat server, which pushes it down B's connection.
When to bend the template
The template is a starting point, not a law. Adjust it when a requirement pushes back:
| Requirement | Change to the template |
|---|---|
| The user must see their own write immediately | Write synchronously to the database (or also to the cache) before responding, or have the client display the write optimistically. See cache strategies. |
| Strong consistency, e.g. payments or seat booking | Skip the async write path for the critical step; use a transaction or locking and respond only after commit. |
| Full-text search | Add a search index fed from the queue or by change data capture. |
| Large files (images, video, documents) | Upload straight to object storage with presigned URLs; serve through a CDN; queue only the metadata and processing jobs. |
| Real-time delivery | Add connection servers that hold WebSockets, as in the chat example. |
| Analytics over all history | Stream events from the queue into a warehouse or stream processor. |
Where it’s used
Posts go through a queue that fans out to cached timelines; reads hit only the cache. See Design Twitter.
Messages flow through a queue to storage, offline inboxes in the cache and the recipient's connection server.
Comments are enqueued and written by workers, and comment threads are read from a cache. See the hands-on comment system build.
Requests are queued so a slow external provider never blocks the user-facing service.
In an interview
- Draw the template as your first high-level design, then adapt it to the problem's specific requirements.
- Say the takeaway out loud: writes go to a queue, reads come from a cache, the database is the source of truth.
- Explain why the queue is there (different processing speeds and durability), not just that it is there.
- Point out the consequence: reads are eventually consistent, and say whether the product can accept that.