System Design Interview

Course Content

System Design Interview

31 sections · 71 lessons

Nearby Friends: scope, scale and why the proximity design fails


The prompt: "Design a nearby friends feature." While the feature is switched on, a user sees which of their friends are currently within a few kilometres of them, updating as everybody moves.

Attempt it for 45 minutes before reading. If you have already read Section 18 (Proximity Service), resist the temptation to reuse it — the last part of this lesson explains why that instinct is wrong, and reaching that conclusion yourself is worth more than being told.

This lesson scopes the feature, puts numbers on a workload that looks nothing like the proximity service's, and then shows precisely why the proximity design does not transfer. That failure is the pivot of the section: it points to a push architecture instead of a query architecture.

What changed since the proximity serviceProximity service• Places barely ever move• The query is asked on demand• Candidates are a whole neighbourhoodNearby friends• Every user moves constantly• The query stands for an hour• Candidates are a short friend list
Same words, different system: the index is now write-dominated and the interesting set is social, not geographic.

What changed from the proximity service

Section 18 found nearby restaurants. This section finds nearby people. The query looks identical and the system is not, for one reason: restaurants do not move and people do not stop moving.

Everything in Section 18 rested on a dataset that changed ten thousand times a day against five hundred million reads. Here every tracked point changes every few seconds. The estimate below puts a number on that, and the section after it shows what it destroys.

The five questions

1. How often does a location update? This is the question. Every 30 seconds and every 5 seconds are different systems, because the write rate scales inversely with the interval. Push for a number, and ask whether the client can vary it.

2. What radius counts as "nearby"? A few kilometres is typical. It matters because it sets the granularity of whatever spatial partitioning you use, and because a large radius in a dense city means a large candidate set.

3. Is location history stored? Say it plainly: storing a movement history is a different product with different obligations. For this feature, the only location that matters is the current one, and it is worthless within a minute. That makes the data ephemeral, which is a gift — The architecture uses it.

4. What are the privacy and opt-out requirements? Not a footnote. A feature that broadcasts a person's position to others needs explicit consent, an instant off switch, and per-friend granularity. Raise it before being asked; Scaling and follow-ups develops it.

5. Which platforms, and what is the battery budget? A mobile client sending a location every 5 seconds with a full radio wake-up will be uninstalled. Battery is a real design constraint here, and it appears again in Google Maps: navigation as a session.

Requirements and scale

Functional: a user opts in and begins sharing location; the client receives updates when a friend enters or moves within the radius; the user sees each nearby friend's approximate distance; either party can switch it off instantly.

Heavy writes, and almost no storage100 M sharingBroker fleetevery 30 s3.3 M writes/s3.3 M per secondNever touch diskabout 400 friendsBounded fan-outLatestposition onlyA few hundred GBNumberConsequenceUsersUpdate intervalLocation writesFriends eachStorage kept
Nothing needs to be durable, which is the pleasant surprise: a lost position is corrected thirty seconds later.

Non-functional: a friend's movement is reflected within a few seconds; the system is available, but a missed update is not a catastrophe — this is a soft real-time feature, not a ledger; location data is ephemeral and never retained beyond a short window; battery cost on the client stays modest.

The numbers

Invented figures for a large social platform. A day is 100,000 seconds (see Rounding aggressively and staying fast).

QuantityAssumption
Users with the feature enabled100 million
Concurrently active (app open, sharing) at peak10 million
Location update intervalevery 30 seconds
Average friends per user200

The write rate:

10,000,000 active users ÷ 30 seconds = 333,000 location updates per second

Set that beside Section 18 (Proximity Service): 5,000 searches per second against a dataset that changed 10,000 times per day. Here the dataset changes 333,000 times per second, which is 33 billion changes a day — roughly three million times the proximity service's write rate — and it exceeds this system's own read rate. Every assumption that made a precomputed index sensible has reversed.

The fan-out. Each update is potentially interesting to the sender's friends who are both online and nearby. Most of a person's 200 friends are neither. Assume on average one or two friends qualify:

333,000 updates/s × ~1.5 interested recipients = around 500,000 pushes per second

Connections. Ten million clients need a live channel to receive pushes. At 100,000 connections per server (the planning figure from the chat system's requirements lesson), that is 100 connection servers.

Storage — and the pleasant surprise

A location record is a user identifier, latitude, longitude, and a timestamp: about 40 bytes.

10 million active users × 40 bytes = 400 MB

The entire live dataset fits in memory several times over on one machine. It also needs no durability whatsoever: if the store is lost, every client re-reports within 30 seconds and the system heals itself. A location older than a minute is not stale data, it is wrong data, and should be deleted rather than served.

That combination — tiny, volatile, self-healing, worthless when old — is unusual and it is what makes an in-memory store with a short time-to-live exactly right here.

Why the proximity service design does not transfer

This is the pivot of the section. Section 18 built a geospatial index and made it fast. The temptation is to reuse it and update it as people move. That fails four different ways, and each failure teaches something.

Four ways the reused design breaksIndex is write-dominatedStanding query, not ad hocSelective set is friendsDurability buys nothing
All four failures point the same way: push updates to subscribers instead of indexing positions for later search.

Failure 1: the index is now write-dominated

The proximity service's numbers were 500 million reads against 10,000 writes per day — a ratio of 50,000 to 1. That asymmetry is what justified a precomputed index: pay a large cost once, amortise it over an enormous number of reads.

Here, 333,000 writes per second exceed the read rate. A precomputed index amortises nothing when it is rebuilt continuously.

Cost the quadtree specifically, since it was the proximity service's best structure for density. Every move means removing a point from one leaf and inserting it into another; leaves that cross their capacity split, and leaves that empty out merge. At 333,000 moves per second against a shared in-memory tree, the structure spends its time rebalancing, and every mutation needs a lock or a copy-on-write generation. The structure is not slow — it is being asked to do something it was never designed for.

Failure 2: the query is standing, not on-demand

The proximity service's query was a user action: someone opened a map, and a query ran. Here the user asks once — "tell me when a friend is near" — and expects answers continuously for an hour.

Implementing a standing query by polling an index is expensive. Suppose each client polls every 5 seconds:

10,000,000 clients ÷ 5 s = 2 million queries per second

Each resolves a 200-friend list and checks positions: 2M × 200 = 400 million lookups/s

Four hundred million lookups per second to discover that almost nothing changed. The proximity service's peak was 20,000 queries per second. This is 100× the query rate on top of a write rate the index cannot absorb.

Failure 3: the selective set is the friend list, not the neighbourhood

The proximity service filtered by geography first because every restaurant in the cell was a valid answer. Here, a cell in a city centre may contain 50,000 people of whom two are your friends. Filtering by geography first means retrieving 50,000 candidates to discard 49,998.

The far more selective filter is friendship: 200 candidates, of whom a handful are nearby. Any design that leads with geography is starting from the wrong end.

Failure 4: durability is a cost with no benefit

The proximity service's index was worth persisting — rebuilding it from 200 million place records is expensive. Here the entire dataset regenerates from clients in 30 seconds. Writing 333,000 locations per second to durable storage buys nothing and costs a great deal.

The shift

Put the four together and the architecture inverts:

Proximity Service (static places)Nearby Friends (moving people)
Read/write ratio50,000 : 1roughly 1 : 1, write-heavy
Query styleOn demand, one answerStanding, continuous
Most selective filterGeographyFriendship
Data lifetimeYears~30 seconds
Right shapePrecomputed index, pulledPush on change, publish-subscribe

The move is from "store it, then let people query it" to "route it to whoever is interested, then throw it away". Nothing is indexed, because nothing is stored long enough to be worth indexing.