Two Blocks, One Critical Distinction
Of the 7 building blocks that make up every system, two are responsible for doing work: the Service and the Worker. Everything else stores data. These two process it.
The difference between them is simple to state and easy to get wrong: a service makes the caller wait for a response. A worker does not.
That distinction sounds trivial. It is not. Getting it wrong is one of the most common architectural mistakes in production systems, and it is the mistake AI coding assistants make almost every time.
The Service: Your System's Front Door

A service receives a request and returns a response. The caller is actively waiting. Think of it like a restaurant waiter: you place your order, you sit there, and the waiter brings your food back to your table.
Every API endpoint you have ever called is a service. You send an HTTP request, and you get a response. The key properties:
- Synchronous: the caller blocks until the response arrives
- Bounded time: the response needs to come back quickly (usually under a few seconds)
- Direct relationship: one caller, one response
Here is what a basic service looks like:
@app.route("/api/profile/<user_id>")
def get_profile(user_id):
profile = cache.get(f"profile:{user_id}")
if not profile:
profile = db.query("SELECT * FROM users WHERE id = %s", user_id)
cache.set(f"profile:{user_id}", profile, ttl=300)
return jsonify(profile)
The caller asks for a profile. The service checks the cache, falls back to the database if needed, and returns the result. Simple, direct, synchronous.
Services are the right choice when the user is waiting for an answer. "Show me my feed." "What is the current price?" "Is this username available?" These are all service calls.
The Worker: Your System's Back Office

A worker picks up tasks and processes them without anyone waiting on the other end. Think of the kitchen in that restaurant. The kitchen does not interact with diners directly. It receives tickets (from a queue), prepares the food, and puts completed dishes on the pass. The kitchen works at its own pace.
Workers have fundamentally different properties than services:
- Asynchronous: no one is blocking on the result
- Self-paced: the worker processes tasks as fast as it can, not as fast as callers demand
- Decoupled: the worker does not know or care who originally submitted the task
You do not call a worker. You put a message on a queue, and the worker picks it up when it is ready.
When does a worker make sense? Whenever the user does not need the result right now. Sending a welcome email after signup. Generating a PDF report. Encoding a video into multiple resolutions. Resizing an uploaded photo into six thumbnail sizes. All worker jobs.
The Mistake Everyone Makes
Here is where junior engineers (and AI) get it wrong: they put slow work inside a service.
Imagine you are building a photo-sharing app. A user uploads a photo, and you need to create thumbnails in five different sizes. The naive approach puts the thumbnail generation inside the upload service:
- User uploads photo
- Service receives photo
- Service generates five thumbnails (takes 8 seconds)
- Service returns "upload complete"
The user stares at a loading spinner for 8 seconds. Now multiply that by a thousand concurrent users. Your service has a thousand threads all blocked on thumbnail generation. Response times spike. Timeouts start. Users retry, making it worse. The system buckles.
The architectural fix:
- User uploads photo
- Service saves the original to a file store (fast)
- Service drops a message on a queue: "process photo #12345"
- Service returns "upload complete" (under 1 second)
- Worker picks up the message and generates thumbnails at its own pace
The user sees instant confirmation. The thumbnails appear a few seconds later. If a million people upload at once, the queue absorbs the spike and workers chew through the backlog steadily. The system stays healthy.
Same outcome. Radically different resilience.
Why AI Gets This Wrong
When you ask an AI coding assistant to "build a photo upload endpoint," it will almost always generate a single service that does everything inline. Upload, validate, resize, store, update the database, return the response. All in one synchronous call.
That works perfectly for a demo. It falls apart in production.
AI generates code. It does not generate architecture. It does not think about what happens when 10,000 users hit that endpoint simultaneously. It does not consider that thumbnail generation is slow work that should not block the response. It does not weigh the tradeoff between user-perceived speed and processing completeness.
Those are your decisions to make. And the first decision is always the same: is this a service job or a worker job? Does the caller need the result right now, or can it happen later?
The Decision Framework
When you encounter a new piece of functionality, ask two questions:
Is someone waiting for this result? If yes, it belongs in a service. If no, it belongs in a worker.
What happens if this takes 30 seconds? If the answer is "the user stares at a spinner and gets frustrated," you need to split the work. The fast part goes in the service. The slow part goes in a worker.
Some real examples:
| Task | Block | Why |
|---|---|---|
| Return a user's profile | Service | User is waiting for data |
| Send a welcome email | Worker | User does not need to wait for email delivery |
| Validate a credit card | Service | User needs to know if payment works before proceeding |
| Generate a monthly report | Worker | Nobody is sitting there refreshing for it |
| Check if a username is taken | Service | User is typing and waiting for validation |
| Encode an uploaded video | Worker | Encoding takes minutes; user should not wait |
The Pattern in Production
At scale, most systems have far more workers than services. Instagram has a handful of service types (feed, stories, search, profile) but dozens of worker types (thumbnail generation, content moderation, notification delivery, recommendation updates, analytics aggregation).
This makes sense when you think about it. The service is the narrow front door. It needs to be fast and focused. The workers are the sprawling back office, handling all the heavy lifting that makes the product actually work.
Understanding this pattern is what separates engineers who build demos from engineers who build systems. And it is the first architectural decision you will make in the interactive challenges when you design Instagram and Netflix yourself.
Next up: The 3 Storage Extremes covers the five storage blocks and how to pick the right one.
Frequently Asked Questions
- What is the difference between a service and a worker?
- They are the two Task blocks, the parts of a system that do work rather than store data, and the difference is simple to state: a Service makes the caller wait for a response, a Worker does not. A Service is your system's front door. It receives a request and returns an answer right now, so it has to be fast, usually under a few seconds. A Worker is the back office. It picks up a task from a Queue and processes it in the background while nobody is waiting on the result. Confuse the two and your system falls apart under load. The whole skill is telling them apart.
- When should you use a service vs a worker?
- Ask one question: is someone waiting for this result? If yes, it belongs in a Service. If no, it belongs in a Worker. Returning a user's profile, checking if a username is taken, validating a credit card before checkout: someone is staring at the screen, so those are Service jobs. Sending a welcome email, generating a monthly report, encoding an uploaded video: nobody is refreshing the page waiting, so those are Worker jobs. A second test sharpens it. What happens if this takes 30 seconds? If the answer is "the user stares at a spinner," split the work: the fast part goes in the Service, the slow part goes in a Worker.
- Why is putting slow work in a service a mistake?
- Because a Service makes the caller wait, and slow work makes them wait too long. Picture a photo upload that generates five thumbnails inside the upload Service. Each upload now takes eight seconds. Multiply that by a thousand concurrent users and you have a thousand threads all blocked on thumbnail generation. Response times spike, timeouts start, users retry, and the system buckles. The fix is to split it: the Service saves the original and drops a "process this photo" message on a Queue, then returns "upload complete" in under a second. A Worker picks up the message and makes the thumbnails at its own pace. Same outcome, radically different resilience.
- Why does AI put everything in one service?
- Ask an AI assistant to "build a photo upload endpoint" and it will almost always generate a single Service that does everything inline: upload, validate, resize, store, update the database, return the response. That works perfectly for a demo and falls apart in production. AI generates code, not architecture. It does not think about what happens when 10,000 users hit that endpoint at once, or that thumbnail generation is slow work that should not block the response. Those are your decisions. And the first one is always the same: is this a Service job or a Worker job? Does the caller need the result right now, or can it happen later?