Nearby Friends / Proximity Service System Design¶
Interview Ready | 45-minute format
Problem Statement¶
Design a service that shows a user which of their friends are nearby, updating in near real time as people move - like Snap Map or "Nearby Friends" on Facebook.
Example: open the app and see friends within 5 km, refreshing every few seconds.
Complements the Uber design (geospatial matching); this focuses on continuous location updates and privacy for a social graph.
1️⃣ Requirements Clarification¶
Functional Requirements:¶
- Users publish their location periodically.
- A user sees friends within a radius, updated in near real time.
- Respect privacy (opt-in, granular sharing).
Non-Functional Requirements:¶
- Scale: 100M users, frequent location updates (bursty writes).
- Low latency: proximity results in <1s.
- Freshness: locations no more than a few seconds stale.
- Privacy: users control who sees them; off means invisible.
Out of Scope:¶
- Turn-by-turn routing; matching strangers (that's ride-hailing/dating).
2️⃣ Capacity Estimation¶
100M users, active 10M, each sends location every 5s
Writes = 10M / 5 = 2M location updates/sec → very write-heavy
Each user has ~200 friends → proximity check is bounded to the friend set, not all users
Store current location in memory (Redis) - ephemeral, TTL'd; history optional
3️⃣ API Design¶
POST /v1/location { lat, lng, ts } # publish my location (every few sec)
GET /v1/nearby?radius_km=5 # friends within radius
# Real-time: WebSocket/pub-sub push of friends' location updates
4️⃣ Key Insight: Proximity is bounded by the friend graph¶
Unlike Uber (match any nearby driver), "nearby friends" only cares about a user's friends - a bounded set (~hundreds). So we don't need a global geo query per request; we compute distance against the user's friends' cached locations. Geospatial indexing still helps when the friend set is large or for "friends in this area" queries.
5️⃣ High-Level Design¶
Devices ──location updates (every ~5s)──►┌──────────────────┐
▲ │ Location Ingest │
│ push friends' updates │ (stateless) │
│ └────────┬─────────┘
┌──┴───────────────┐ ▼
│ Pub/Sub layer │◄─────────────────── ┌──────────────────┐
│ (Redis / WS) │ publish update │ Location Store │
└──────────────────┘ to friends' │ Redis GEO / TTL │ current loc only
channels │ (sharded by user)│
└──────────────────┘
▲
┌──────┴───────┐
│ Friend graph │ who are my friends?
└──────────────┘
Flow: device posts location → ingest updates the location store (with TTL) → publishes the
update to the user's friends via pub/sub → each friend's device (subscribed) receives it and updates
the map. On demand, GET /nearby reads friends' cached locations and filters by radius.
6️⃣ Deep Dives¶
Deep Dive 1: Geospatial indexing (geohash / quadtree)¶
To answer "who/what is near this point," encode locations with a geohash (a string prefix where
nearby points share prefixes) or a quadtree (recursively subdivided cells). Both let you query a
cell + neighbors instead of scanning everything. Redis GEO commands (backed by geohash) give
GEOSEARCH ... BYRADIUS out of the box. Uber uses similar grid indexing - see Uber.
Deep Dive 2: Handling the write storm¶
2M updates/sec is the hard part. Mitigations: adaptive update frequency (send less often when stationary), store only the latest location (overwrite, TTL - no history), shard the location store by user ID, and treat updates as fire-and-forget. Losing one update is fine; the next arrives in seconds.
Deep Dive 3: Real-time delivery via pub/sub¶
Each user subscribes to a channel; when a friend's location changes, publish to that channel and push over a WebSocket. This is push (not poll) so the map updates live. For users with many active friends, batch updates over a short window to cut message volume.
Deep Dive 4: Privacy¶
Location is sensitive: sharing is opt-in and granular (per friend / ghost mode / precise vs approximate). If a user goes invisible, stop publishing and drop their cached location. Never expose location to non-friends. This is a first-class requirement, not an afterthought - see Security Fundamentals.
Deep Dive 5: Staleness & TTL¶
Store current location with a short TTL so a user who goes offline simply disappears (their key expires) rather than showing a stale position. Freshness beats history here.
7️⃣ Bottlenecks & Optimizations¶
- Write throughput → adaptive frequency, latest-only writes, shard by user.
- Fan-out to many friends → batch/window updates; pub/sub per channel.
- Hot areas (concerts, cities) → geospatial cells still bound the query; cache popular cells.
- Battery/data on device → back off updates when stationary.
8️⃣ Interview Tips¶
- Call out the key difference from Uber: bounded by the friend set, not a global match.
- The write storm is the real challenge - adaptive frequency + latest-only + TTL.
- Use geohash/quadtree (or Redis GEO) for area queries.
- Treat privacy as a core requirement.
Common Follow-ups:¶
Q: How do you handle 2M updates/sec? Adaptive frequency, latest-only + TTL, shard by user, fire-and-forget. Q: How do you find who's nearby? Geohash/quadtree cells (or Redis GEO), filtered to friends. Q: How is this different from Uber? Bounded to friends, continuous updates, privacy-first. Q: How do you push updates live? Pub/sub + WebSocket to subscribed friends.
Summary¶
Key Decisions: 1. ✅ Proximity bounded by the friend graph (not a global geo scan). 2. ✅ Geohash/quadtree (Redis GEO) for area queries. 3. ✅ Handle the write storm: adaptive frequency, latest-only + TTL, shard by user. 4. ✅ Pub/sub + WebSocket for real-time delivery; privacy (opt-in, ghost mode) first-class.
Related: Uber · Security Fundamentals · Caching