Why this matters
- Snapchat Map, Find My, and StreamHub's "friends watching nearby" share the same pattern: frequent writes, infrequent reads, strict privacy boundaries.
- Unlike ride-hailing (millions of strangers), friend graphs are sparse — you query locations for 50–500 friends, not the entire user base.
- Battery drain from constant GPS polling is a product killer; adaptive update intervals are a design requirement, not an optimisation.
Nearby Friends components
- Location publisher — client sends lat/lng on interval (adaptive: 30s stationary, 5s moving).
- Friend graph service — bidirectional friendship with privacy settings (share with all, select friends, ghost mode).
- Location store — per-user latest position with TTL; Redis GEO or geohash-indexed KV.
- Query service — given user A, fetch friend IDs, batch-lookup positions, filter by radius and privacy.
- Push notifier — alert when a friend enters your radius ("Alex is 500m away").
Architecture overview
Proximity / ride-matching
PUT /v1/location
Authorization: Bearer <token>
Content-Type: application/json
{
"lat": 37.7749,
"lng": -122.4194,
"accuracy_m": 12,
"timestamp_ms": 1740847200000
}
GET /v1/friends/nearby?radius_m=1000
Authorization: Bearer <token>
→ 200
[
{ "user_id": "usr_42", "lat": 37.7751, "lng": -122.4188, "distance_m": 320, "updated_at": "..." },
{ "user_id": "usr_99", "lat": 37.7730, "lng": -122.4210, "distance_m": 890, "updated_at": "..." }
]
Privacy model
| Aspect | Setting | Behaviour |
|---|---|---|
| Ghost mode | User invisible to everyone | Location not stored or published |
| Select friends | Whitelist of user IDs | Only listed friends see location |
| Approximate location | Snap to 1 km grid | Hides exact address while showing area |
| Time-limited sharing | Auto-expire after 8 hours | Reduces forgotten-sharing risk |
Ghost mode
SettingUser invisible to everyoneBehaviourLocation not stored or publishedSelect friends
SettingWhitelist of user IDsBehaviourOnly listed friends see locationApproximate location
SettingSnap to 1 km gridBehaviourHides exact address while showing areaTime-limited sharing
SettingAuto-expire after 8 hoursBehaviourReduces forgotten-sharing risk
Privacy checks happen server-side on every query — never trust the client to filter friends.
Efficient friend-based lookup
Unlike Uber's global spatial index, Nearby Friends inverts the query: start from the friend list, then fetch locations.
def get_nearby_friends(user_id: str, radius_m: int) -> list:
friend_ids = friend_graph.get_friends(user_id)
user_location = location_store.get(user_id)
results = []
for friend_id in friend_ids:
if not privacy_allows(friend_id, user_id):
continue
friend_loc = location_store.get(friend_id)
if friend_loc is None or is_stale(friend_loc, max_age_s=120):
continue
dist = haversine(user_location, friend_loc)
if dist <= radius_m:
results.append({**friend_loc, "distance_m": dist})
return sorted(results, key=lambda r: r["distance_m"])
Adaptive update intervals
{
"update_policy": {
"stationary_interval_s": 60,
"moving_interval_s": 10,
"speed_threshold_mps": 1.5,
"significant_change_m": 50
}
}
Clients increase interval when stationary (saving battery) and decrease when moving. Server-side geofencing can skip writes when position change is below the significance threshold.
Geofence notifications
When friend B enters a radius around friend A, push a notification.
{
"event": "friend.entered_radius",
"viewer_id": "usr_01",
"friend_id": "usr_42",
"distance_m": 480,
"timestamp": "2026-04-01T16:00:00Z"
}
Evaluate geofence crossings in a streaming processor (Kafka + Flink) that maintains last-known positions and friend-pair subscriptions.
Scale estimates
| Metric | Estimate |
|---|---|
| Users with location sharing enabled | 20M |
| Average friends per user | 150 |
| Location updates per user per hour | ~120 (adaptive) |
| Write QPS | 20M × 120 / 3600 ≈ 670K |
| Nearby query QPS | ~50K peak |
| Storage per user | ~200 bytes (latest position only) |
Users with location sharing enabled
Estimate20MAverage friends per user
Estimate150Location updates per user per hour
Estimate~120 (adaptive)Write QPS
Estimate20M × 120 / 3600 ≈ 670KNearby query QPS
Estimate~50K peakStorage per user
Estimate~200 bytes (latest position only)
Friend-list lookup is O(friends), not O(users). 150 Redis GETs per query is fast with pipelining.
Data retention and compliance
Location data is sensitive PII. Apply strict retention policies.
Compliance requirements
- TTL on location records — auto-delete positions older than 24 hours unless user opts into history.
- Audit log — record who queried whose location and when, for abuse investigations.
- GDPR/CCPA deletion — cascade delete location data when a user deletes their account.
- Encryption — TLS in transit; encrypt at rest in the location store.
Quick recall
Everything you need if you only revisit this box.
- Nearby Friends = friend-graph-first lookup, not global spatial scan.
- Privacy controls (ghost mode, select friends, approximate location) enforced server-side.
- Adaptive GPS intervals save battery: longer when stationary, shorter when moving.
- Batch-fetch friend locations with pipelined Redis MGET for sub-20 ms queries.
- Strict TTL, audit logs, and encryption — location is sensitive PII.
Test yourself
Answer these before moving on — recall is what makes it stick.