You open Uber, and within seconds a map shows cars circling near you. You tap a button, and somewhere out in the city a specific driver's phone lights up with your ride. You watch their car crawl toward you on the map in real time, the little icon nudging block by block. The price was set before you tapped. When the trip ends, your card is charged and a receipt appears without you doing a thing. All of that, every time, for millions of riders and drivers moving at once.
Most explanations of "Uber system design" hand you a wall of vendor names. Use this database here. A geospatial index there. A message broker in the middle. You memorize the diagram for an interview, and you forget it by the weekend, because a list of products is not understanding.
Here is the thing those explanations miss. Uber is not a special, secret machine. It is built from the same small set of parts that every large system is built from — the same 7 building blocks behind Instagram, Netflix, and Stripe. Once you can see those parts, you stop memorizing Uber's architecture and start being able to read any system, including the ones you have not seen yet.
This breakdown uses one lens: the 7 building blocks. Seven reusable parts that compose into Uber, into Instagram, into Stripe, into the system you are about to be handed at work. We will walk Uber's core flows, requesting and matching a ride, tracking a car in real time, surge pricing, and the trip and payment, and trace each one through the blocks it touches. Uber leans harder on two of those blocks than a photo app ever does: the Queue that matches riders to drivers, and the fast storage that holds where every car is right now. By the end you will not have memorized Uber. You will have a way of seeing.
The 7 building blocks, plainly
Before we trace anything, here are the parts. Do not assume you know them. Most "system design" writing throws these words around as if everyone agrees on them. They do not. So here is the plain version.
Every part of a system is doing one of two jobs: handling work or holding data.
Work splits into exactly two modes. That is the complete set. There is no third mode.
- Service (handles work while someone waits). A Service answers a request and returns a response right now. You open the app, the app asks a Service for nearby cars, the Service answers. The benefit is immediacy. The cost is that the user is blocked until it responds, so a Service must be fast.
- Worker (handles work after the user is gone). A Worker does a job in the background, when no one is waiting on the result. The benefit is that slow work does not make the user wait. The cost is that the answer is not instant, so you only use a Worker for things that can finish a few seconds or minutes later.
Holding data splits into five kinds, by the shape of the data and the question you ask it.
- File Store (holds big binary files). Photos, videos, audio. The benefit is cheap, near-infinite storage for large files you fetch by their address. The cost is that you cannot ask it questions like "find all files from Tuesday." It just stores and serves the file.
- Relational Database (holds structured records and the relationships between them). Users, trips, payments, who drove whom. The benefit is that you can ask complex questions across related records ("every trip this rider took last month and what each one cost"). The cost is that those questions get slow at very large scale, which is exactly the tension that forces the next block.
- Key-Value Store (holds one value you look up by one key, very fast). Hand it a key, it hands back the value, in well under a millisecond. The benefit is raw speed. The cost is that it only answers "what is the value for this exact key," nothing richer.
- Queue (holds a line of work waiting to be done). It is a waiting line. Something drops a task in one end; a Worker picks it up from the other. The benefit is that the system can say "got it" instantly and do the work later, and the line absorbs sudden spikes. The cost is that the work is not done the moment you accept it.
- Vector Database (holds data by meaning, for similarity search). It answers "what is most similar to this." It powers recommendations and search-by-meaning. Uber's core ride flows do not need it, so we will name it honestly and set it aside for this breakdown rather than invent a use for it.
That is seven parts. Two for work, five for data. Plus three outside forces that act on the system: the User (a person taking an action, here both the rider and the driver), an External Service (a third party you call, like a payment processor or a maps provider), and Time (the clock itself, triggering things on a schedule or after a delay).
Now watch how Uber falls out of these parts.
Flow 1: Requesting and matching a ride
This is the flow that makes Uber Uber, and it uses a block the photo apps barely touch. You tap "Request," and a few seconds later a specific driver is on the way. Between those two moments is the hardest problem in the system. Let us trace it.
The request hits a Service. Your phone tells Uber's API, which is a Service, where you are and where you want to go. Something is waiting (you, watching the screen say "Finding your driver"), so this is synchronous work by definition. But the Service does not pick your driver on the spot. Picking a driver well is not a "return an answer this instant" problem. It is a "find the best match out of the cars nearby, and do not hand the same car to two riders" problem. So the Service does the same move every well-built system makes when the real work is harder than the wait should be.
It drops the request onto a Queue. The Service writes "this rider, here, wants a ride to there" onto a Queue and tells your phone "looking for a driver." The Queue is a waiting line of ride requests. This is the block Instagram only used for background chores like image processing. Uber puts it at the center of its most important flow, because matching is exactly the kind of work that should be accepted instantly and resolved a moment later, and because the Queue lets the system absorb a sudden flood of requests (think the instant a concert lets out) without falling over.
A Worker does the matching. A Worker, pulling requests off the Queue, runs the actual match. To do that it needs to know which drivers are near you right now, which brings in the next block.
Live driver locations live in a Key-Value Store. Every active driver's phone reports its location constantly. The system needs to answer "which cars are near this point, right now" in milliseconds, for an enormous number of lookups. That is not a question you ask a Relational Database on every request at this scale; it is too slow. It is a raw-speed lookup, which is the job of a Key-Value Store: the current location of each driver, indexed so the matching Worker can grab the cars near you almost instantly. The Worker reads the nearby cars, picks one, marks it as taken so no other Worker grabs it, and notifies the driver.
So one tap touches four blocks working together. A Service to receive the request. A Queue to hold it. A Worker to run the match. A Key-Value Store holding where every car is. The reasoning, not the product names, is what transfers: when the real work is harder than the wait should be, you accept the request on a Service, park it on a Queue, and let a Worker resolve it.
Flow 2: Tracking the car in real time
You have a driver. Now their car icon glides across your map, updating every second or two, the whole way to you and the whole way to your destination. This is the part of Uber that "feels real-time," and it is a different kind of pressure on the system than anything a feed app deals with.
The pressure is a flood of small writes. Every moving car is sending its location every few seconds. Multiply that by every active driver in every city, and you have a torrent of tiny updates pouring in constantly. Each update is small. There are just an overwhelming number of them, all the time. A Relational Database is the wrong tool for that firehose: every write would compete for the same structured tables, and you do not need to run rich queries against a location that will be stale in two seconds anyway.
So live location goes into fast, hot storage. The current position of each car goes into a Key-Value Store, the same kind of fast storage that powers matching. The key is the driver, the value is where they are this second. Writes overwrite the last position; reads grab the latest one. It is built for exactly this: enormous volumes of "set this key, get this key" with no waiting. This is what "the clock is running" does to a design. In a content system, data is written once and read many times. In a real-time system like Uber, the same fact (where is this car) is written over and over and read constantly, both at once, and that forces you toward storage built for speed over storage built for rich questions.
A Service reads it for your map. When your app asks "where is my driver," a Service does a fast lookup against that hot storage and streams the position back to your phone, which animates the icon. The trip itself, meanwhile, is being recorded for later: the route, the timestamps, the distance. That durable record is structured data you will need to query (for the receipt, for support, for the driver's earnings), so it belongs in a Relational Database, written as the trip progresses, not on the hot path that feeds your live map.
Trace this flow and you see the real-time split clearly. Fast, disposable, constantly-overwritten location data goes in a Key-Value Store for speed. The permanent, structured record of the trip goes in a Relational Database for questions you will ask later. Telling those two apart, what needs to be fast versus what needs to be queryable, is one of the most reusable judgments in system design.
Flow 3: Surge pricing
Your price was set before you ever tapped Request, and on a busy night it was higher. Surge pricing looks like a pricing feature. Under the blocks, it is a background calculation driven by the clock, and it shows off the Time entity doing real work.
Nobody computes surge while you wait. The price you see has to appear the instant you open the app. If the system tried to calculate the demand-versus-supply balance for your exact area in the moment you asked, you would be staring at a spinner. So that calculation does not happen on your request at all. It happens ahead of time, on a schedule.
Time triggers a Worker that does the math. On a regular interval, the external force Time wakes up a Worker. That Worker looks at the current balance in each area: how many people are requesting rides versus how many drivers are available. Where demand outruns supply, it computes a higher multiplier. Where things are calm, the multiplier is one. This is a textbook Worker job: it is not tied to any single user's tap, nobody is waiting on it, and it runs on the clock rather than on a click.
The result is cached for instant reads. The Worker writes the current multiplier for each area into a Key-Value Store, keyed by area. Now when you open the app, the Service that shows your price does one fast lookup: what is the multiplier for your area right now. The expensive thinking already happened in the background; your request just reads the answer. That is the same pre-compute-and-cache shape that builds a social feed ahead of time, applied to pricing: do the heavy work on a Worker on a schedule, store the finished answer in fast storage, and let the live request simply look it up.
Surge pricing, then, is four ideas you have already seen. Time as the trigger. A Worker for the work nobody waits on. The demand and supply data it reads. And a Key-Value Store holding the finished number for an instant read. No new machinery, just a new arrangement.
Flow 4: The trip, the payment, and the receipt
The ride ends. You get out. Seconds later your card is charged and a receipt lands in your inbox, and you did nothing. A lot happens in that quiet moment, and it pulls in the outside world.
The trip record is finalized in a Relational Database. When the trip completes, the system writes the final, authoritative record: who the rider was, who the driver was, the route, the distance, the duration, the fare. This is deeply structured, related data, and it is the source of truth for the receipt, the driver's payout, your trip history, and customer support. A Relational Database is built for exactly this: records and the relationships between them, queryable later in any direction.
The charge is not something Uber does alone. Uber does not run a credit card network. To actually move money, it calls out to a payment processor, which is an External Service: a third party the system hands the charge to. And forcing you to stand on the curb watching a "charging your card" spinner would be a poor design. So the work is deferred: when the trip ends, the Service drops a "charge this trip" task onto a Queue and lets you go. A Worker picks it up, calls the payment External Service to run the charge, records the result back in the Relational Database, and then triggers the receipt.
Maps and the receipt lean on the outside world too. The map and routing throughout the trip come from a mapping External Service, another third party Uber calls rather than building from scratch. And once the charge succeeds, sending the email or push receipt is itself background work: another Worker, often calling an email or notification External Service, delivers the receipt to the User while you are already walking away.
One ended trip touches a Relational Database (the permanent record), a Queue and a Worker (the deferred charge), two External Services (payments and maps), and the User on the other end of the receipt. The judgment that matters is the same one from every flow: the part you wait on stays on a Service, and everything that can happen after you have walked away, the charge, the receipt, the call out to other companies, goes onto a Queue and a Worker.
The point is not Uber
Step back and look at what we actually did. We never memorized Uber's architecture. We took four flows and asked the same questions each time. Who is waiting on this? What shape is this data? What can happen in the background? And the same handful of parts answered every time.
- A Service for the work someone waits on.
- A Queue and a Worker for the work that can happen after they are gone, which at Uber means the matching itself, the surge math, and the payment.
- A Key-Value Store for the things that have to be fast: where every car is right now, and the current surge multiplier.
- A Relational Database for the permanent, structured records: trips, payments, who drove whom.
- Time for what the clock triggers, like the surge calculation, and External Services for the payments, maps, and notifications Uber does not own.
Notice what changed from a content app like Instagram. Uber leans on the Queue at the heart of its most important flow, not just for background chores, because matching a rider to a driver is work you accept now and resolve a moment later. And it leans on fast Key-Value storage for a constant firehose of location writes, because the clock is running and the same fact gets written and read over and over at once. Those are the marks of a real-time system. The blocks did not change. The arrangement leaned harder on the parts built for speed and for absorbing spikes.
Here is what is worth holding onto. The exact same questions, asked of Instagram, of Stripe, of Slack, of the feature your team is about to hand you, produce the architecture of those systems too. The blocks do not change. Only the arrangement does. That is why Uber was a useful thing to take apart: not because you will rebuild Uber, but because reading it taught you to read the next one.
The AI Era Lesson
You do not memorize how Uber works. You learn to see its parts, and then you can read any system.
That shift, from memorizing systems to seeing the parts they are made of, is the entire skill. It is the difference between an engineer who can recite a famous architecture and one who can walk into an unfamiliar system and reason about it out loud. AI can write the code either way. The judgment about which parts a system should be made of, and why, is yours to build, and it is the part that lasts.
What to Explore Next
Uber's flows build on the core articles: the Service and Worker split, the three storage extremes that decide Key-Value vs. Relational, the Queue it puts at the heart of matching, and the external entities (User, External Service, Time) pushing on the system from outside.
For another real system traced end to end, see how Instagram actually works and how Netflix actually works. To go deep on the one storage block Uber's ride flows do not need, but every AI search and recommendation system does, read what a vector database is. And when you want to choose the right blocks for your own design, the decision framework pulls all of it into a practical guide.
Frequently Asked Questions
- How does Uber match riders with drivers?
- When you request a ride, a Service receives it but does not pick a driver on the spot. It drops the request onto a Queue, and a Worker pulls it off and runs the actual matching in the background, which is why the app says "finding your driver" for a moment. To choose a car, the Worker reads the live positions of nearby drivers from a Key-Value Store (fast storage that answers "where is this driver" in under a millisecond), picks one, and marks it taken so no other rider gets the same car.
- How does Uber track driver location in real time?
- Every active driver's phone reports its location every few seconds, which creates an enormous, constant flood of small writes. Those live positions go into a Key-Value Store, fast storage built to handle huge volumes of "set this key, get this key" where each new position simply overwrites the last. When your app asks where your driver is, a Service does a quick lookup against that hot storage and streams the position to your phone, which animates the car on your map.
- How does surge pricing work?
- The price is not calculated while you wait. On a schedule, the external force Time triggers a background Worker that compares ride demand against available drivers in each area and computes a multiplier, higher where demand outruns supply. That Worker stores the finished multiplier for each area in a Key-Value Store, so when you open the app, the Service showing your price does one fast lookup instead of any live calculation.
- What database does Uber use?
- There is no single database, and that is the actual lesson. Uber uses a mix, and each kind of storage is chosen by the job it does: a Relational Database for the permanent, structured records like trips, payments, and who drove whom; and a Key-Value Store for things that must be fast, like every car's live location and the current surge multiplier. The point is never the vendor name. It is matching the shape of the data and the question you ask it to the block built for that job.