Free preview

The QuadTree Under Write Pressure

In one line: this is the chapter's central engineering problem. The Yelp chapter built a quadtree for points that never move; this chapter asks it to hold three million points that move every four seconds.

The location manager

Riders and drivers connect to the location manager service. It shows nearby drivers when a rider opens the app and receives driver location updates every ~4 seconds. The service forwards driver locations to the quadtree map service to determine the driver's current map segment. It also stores each driver's latest location and, during trips, records the route taken.

One service, four jobs, on very different schedules

Worth separating, because they have almost nothing in common operationally:

JobRateConsumer
Ingest driver updates750,000/sEverything downstream
Serve nearby-driver browsingEvery app openRiders
Store latest position750,000/s, overwritingMatching
Record route tracesPer in-progress trip, appendingBilling, fraud, analytics

The third and fourth are the pair to notice. Latest position is a mutable key-value write; a route trace is an immutable append. Same input stream, opposite storage shapes — which is precisely the polyglot split Lesson 10 makes, and precisely the workload Lesson 3 found the estimate had omitted.

The second is the odd one out: the only read in a service otherwise dedicated to absorbing writes. That mixture — a firehose of writes and a stream of browse queries against the same data — is what makes this component hard.

Why the Yelp quadtree does not survive

The most significant difference we have now is that our quadtree wasn't designed with regular upgrades in consideration.

The data structures must handle location updates from active drivers approximately every 4 seconds. Each location update may require modifying the quadtree. To process an update, the system determines the driver's current region based on the previous position. If the driver crosses region boundaries, the system removes the entry from the previous region and inserts it into the new region. If the updated region exceeds the configured driver threshold, it is subdivided further.

Every property that made the Yelp quadtree cheap is reversed here

Put the two chapters side by side:

YelpUber
PointsStatic — a cafe stays putMoving — every 4 seconds
Insert rate5 per day750,000 per second
Boundary crossingsNeverConstantly
SplitsRare, and expensive was fineContinuous, and expensive is fatal
MergesNot neededRequired — cells empty as drivers leave

The Yelp chapter's conclusion was that "an operation's cost only matters multiplied by its frequency" — splits were expensive and completely acceptable at five writes a day.

Multiply the same cost by 750,000 per second and the design collapses.

And boundary crossings are not an edge case here — they are the normal state. A driver moving at 50 km/h covers 55 metres between updates, so in a dense city where leaves are small, a large fraction of updates cross a cell boundary. Each crossing is a remove plus an insert, and each insert risks a split.

Worse, the reverse operation now matters. Yelp never needed a merge, because places do not leave. Here drivers stream out of a region as constantly as they stream in, so cells empty and — without merging — the tree accumulates depth for density that has moved on.

A data structure's cost profile is a function of its workload, not of the structure. The quadtree is right for both chapters; what changes is everything about how you maintain it.

The fix: decouple the two questions

To overcome the above problem, we can use a hash table to store each driver's latest position and update our quadtree every 10–15 seconds. We can update the driver's location in the quadtree every 15 seconds instead of every 4 seconds, and we use a hash table that updates every 4 seconds and reflects the driver's latest location.

This works because the two structures answer different questions

The trick looks like a caching hack. It is better than that — it is a recognition that "where is driver X?" and "which drivers are near point P?" are different queries deserving different structures.

Hash tableQuadTree
QuestionWhere is driver X?Which drivers are near P?
KeydriverIDSpatial region
Write costO(1) overwriteRemove, insert, maybe split
Update rateEvery 4 sEvery 10–15 s
FreshnessExactUp to 15 s stale

A hash table keyed by driver ID absorbs a positional update in constant time with no structural change — it is just overwriting a value. It is the perfect shape for a firehose of updates to known keys.

A quadtree cannot do that, because its whole value is organizing points by position, and changing a point's position is therefore a structural change.

So: put the firehose where it is cheap, and let the expensive structure lag.

The arithmetic of the relief:

QuadTree writes at 4 s  = 750,000/second
QuadTree writes at 15 s = 200,000/second     <- 3.75x reduction

Still enormous, and it is why Lesson 11 adds Redis in front. But a 3.75× reduction on the most expensive operation in the system is a large win for one parameter change.

When one structure is cheap to write and another is cheap to query, use both and accept lag between them. That is the same instinct as that building block's base-versus-transitory edge weights and that building block's semantic cache: materialize what is stable, compute or defer what is volatile.

Naming what the 15-second lag actually costs

The design says the lag is fine. It is worth being specific about why, and where it is not.

A driver at 50 km/h travels about 208 metres in 15 seconds. On a motorway, closer to 400. So the indexed position can be a few hundred metres from the truth.

That is acceptable for finding candidates — a driver 300 metres from where the index thought is still a fine match, and the exact position comes from the hash table once a specific driver is selected.

It is not acceptable for two things the design does not separate:

Showing cars on the rider's map. A car that jumps 200 metres every 15 seconds looks broken. This is why real apps interpolate client-side — the visual smoothness is a rendering concern, fed by the 4-second stream rather than the index.

Matching in a fast-moving situation. A driver who has just entered a motorway may be several hundred metres past the rider by the time they are offered the trip. Real systems weight by heading and speed, not just position — which the hash table's old-and-new coordinates from Lesson 5 make possible.

The honest framing: the index's job is recall, not precision. It must not miss a nearby driver; being slightly wrong about exactly where they are is harmless because a second, exact lookup follows.

What the design still owes: merges, hot cells, and the browse query

Three problems the two-tier scheme does not solve.

Merging. The design describes splitting and never merging. Drivers leave regions constantly — a stadium empties, rush hour ends — so cells that split during a peak stay split. Without merges the tree deepens permanently. The Yelp chapter had the same gap and answered it with a periodic rebuild; here the churn is far higher, so a rebuild needs to be far more frequent, or the tree needs real merge logic.

Hot cells. An airport arrivals lane or a stadium after a concert concentrates thousands of drivers into one small area. The threshold forces repeated splits until cells are metres across, and the tree becomes extremely deep in exactly the place with the most traffic. Some systems cap depth and accept larger leaves rather than fragmenting indefinitely.

Concurrent reads during restructuring. Same problem that building block raised — a rider's browse query traverses the tree while a split is in flight. At five splits a day it was a corner case; at this churn it is the normal state, and copy-on-write stops being an optimization and becomes mandatory.

Raising the merge gap unprompted is a strong move, because it is the one that follows directly from "the points move" and it is the one the design most clearly did not consider.

Sending fewer pings without losing accuracy

The write volume is set by a number the design chooses: how often each driver reports position. Treating that as fixed is the missed opportunity.

Accuracy is not a function of ping frequency; it is a function of how much the position changes between pings. A parked driver's location is perfectly accurate after one report, and reporting it again every five seconds adds cost and no information.

So let the client vary its interval on speed, heading, proximity to open requests, and driver status. A driver idling at home is nearly free to track; one moving quickly through a busy area gets the frequency that actually matters. The server can also interpolate between reports for a driver travelling predictably, which lets the interval stretch further without the matching service seeing a staler position.

This is the cheapest available reduction in the system's dominant load, and it costs only client-side logic.

Key takeaway

Every property that made the Yelp quadtree cheap reverses here: points move, inserts run at 750,000 per second, boundary crossings are the normal case rather than an edge case, and merges become necessary because drivers leave regions as fast as they arrive. The fix is to recognize that "where is driver X?" and "which drivers are near P?" are different questions: a hash table absorbs the firehose in O(1) with no structural change, while the quadtree lags by 10–15 seconds — a 3.75x reduction in the most expensive operation. The 15-second lag costs a few hundred metres of positional error, which is fine because the index's job is recall, not precision — an exact lookup follows. Still unsolved: merges, hot cells at airports and stadiums, and concurrent reads during splits, which now make copy-on-write mandatory rather than optional.

Interview signal by level

LevelWhat a strong answer sounds like
L4"We use a quadtree like the Yelp design to find nearby drivers, and update it as drivers move."
L5Sees the write problem: "the difference from Yelp is that drivers move, so every update may cross a cell boundary and trigger a remove, insert, and possibly a split. We put a hash table in front for the latest position and update the tree every 10 to 15 seconds instead."
Staff+Names why two structures: "'where is driver X' and 'which drivers are near P' are different queries — a hash table absorbs 750,000 writes per second in O(1) with no structural change, and the quadtree lags, which cuts its write rate 3.75-fold. The 15-second lag costs a couple of hundred metres, which is fine because the index provides recall and the hash table provides precision. I'd flag three gaps: there's no merge operation even though drivers leave regions constantly, so the tree only deepens; hot cells at airports and stadiums fragment indefinitely unless you cap depth; and at this churn rate copy-on-write for concurrent readers stops being optional."

Next: matching a rider to a driver.

Enjoying the preview?

Create a free account to unlock the rest of this course, the in-browser judge, and live AI mock interviews.

Sign up free to continue