Courses › System Design › Question Breakdowns
Design Strava
Idempotent offline uploads, a GPS processing pipeline, spatial segment matching and best-effort leaderboards.
Strava lets runners and cyclists record workouts with GPS, see their stats and maps, compete on segments (popular stretches of road or trail with leaderboards), and follow friends' activities. It's a question about ingesting and processing GPS data (often recorded offline), matching activities against segments efficiently, leaderboards over huge numbers of efforts, and a social feed.
Requirements
Functional
- Users can record an activity (run, ride) with GPS on their phone or watch, even without connectivity
- Users can upload the activity and see its stats and map
- Activities are matched to segments, and users see their times on segment leaderboards
- Users see a feed of activities from people they follow
Out of scope: route planning, training plans, clubs, kudos and comments (beyond a mention).
Non-functional
- Scale: ~100M users; ~5M activities uploaded per day, heavily peaked on weekend mornings
- Recording works offline; uploads must never lose an activity, even with retries
- Processing latency: stats within seconds; segment results within a minute or two
- Durability: raw GPS files are the source of truth and kept forever
[!TIP] The insight that frames the design Recording happens on the device; the server only processes completed activities. So the hard parts aren't real-time streaming — they're a reliable upload and an asynchronous processing pipeline, with segment matching as the expensive geospatial step.
Capacity estimates
Core Entities
- Activity — id, athlete, type, start time, distance, duration, elevation, polyline (simplified route), privacy, raw file key
- Segment — id, name, polyline, bounding box, distance
- Segment effort — segment, activity, athlete, elapsed time, date
- Follow — follower, followee
API
POST /v1/uploads → { uploadId, uploadUrl } (presigned)
PUT {uploadUrl} (raw GPS file)
GET /v1/uploads/{uploadId} → { status: processing | ready, activityId }
GET /v1/activities/{id} → stats, map polyline, segment efforts
GET /v1/segments/{id}/leaderboard?filter=overall|friends|this_year
GET /v1/feed?cursor=High-Level Design
app = client "Phone / Watch" @0,1
api = service "Upload API" @1,1
raw = blob "Raw GPS Files" @1,2
q = queue "Processing Queue" @2,1
proc = service "Activity Processor" @3,1
db = store "Activities DB" @4,0.5
seg = service "Segment Matcher" @3,2
lb = cache "Leaderboards" @4,2
feed = service "Feed Fan-out" @3,0
app -> api : request upload URL
app -> raw : upload file
raw --> q : upload event
q -> proc
proc -> db
proc -> seg
seg -> lb
proc --> feed- Record: the device records GPS points locally and keeps the file until the upload is confirmed.
- Upload: the app gets a presigned URL and uploads the raw file directly to blob storage; an upload event enqueues processing.
- Process: a worker parses the file, cleans GPS noise, computes stats, simplifies the route into a polyline and saves the activity.
- Match segments: the segment matcher finds segments the route covers, computes effort times and updates leaderboards.
- Feed: the activity is fanned out to followers' feeds.
Deep Dives
1. Reliable upload from flaky connections
- The device keeps the activity until the server confirms it. Each activity has a client-generated ID used as an idempotency key, so a retried upload never creates a duplicate.
- Direct-to-blob upload with presigned URLs keeps big files off the API servers. See Handling Large Blobs.
- Processing is triggered by the blob event via a queue, so a crash in the processor just means the message is retried.
2. Processing pipeline
- Parse and clean: drop GPS outliers (impossible jumps), smooth noise, detect pauses.
- Compute stats: distance, moving time, pace, elevation gain (using a terrain model rather than noisy GPS altitude).
- Simplify the route (Douglas–Peucker) into a compact polyline for maps and feeds; keep the raw file for re-processing.
- Privacy zones: trim the start and end of routes near a user's home before anything is shown publicly.
Processing is a natural fit for queues and stateless workers that autoscale on queue depth to absorb the weekend peak. See Managing Long-Running Tasks.
3. Matching activities to segments
Comparing every activity against 50M segments is impossible. Narrow it down spatially first:
[Activity route] -> [Covering grid cells / bounding box] -> [Candidate segments from spatial index] -> [Precise path match] -> (Segment efforts)- Index segments by the grid cells (geohash or S2) their start points fall in, or in an R-tree on bounding boxes.
- For an activity, compute the cells its route passes through and fetch candidate segments — usually dozens, not millions.
- For each candidate, check precisely: does the route pass near the segment's start, follow its path within a tolerance, and reach its end in order? If so, compute the elapsed time between start and end crossings.
See Finding What's Nearby and Database Indexing.
4. Segment leaderboards
Popular segments have millions of efforts; leaderboards show each athlete's best time, with filters (overall, this year, friends, age group).
- Update the sorted set only when an effort beats the athlete's previous best (
ZADD ... LT). - Friends leaderboards are computed on read: fetch the friends' best times from a per-athlete best-efforts table and sort — the friend list is small.
- The durable source of truth is the efforts table (Cassandra, partitioned by segment and year); Redis can be rebuilt from it. See Design a Leaderboard.
5. The activity feed
- Fan-out on write to followers' feed lists for normal users; fan-out on read for accounts with millions of followers (pros). This is the classic hybrid from Fan-out Feeds.
- Feed items store only the activity ID and a summary (distance, polyline thumbnail), fetched from a cache.
6. Re-processing and corrections
- Because raw files are kept, the system can re-run processing when algorithms improve (better elevation data, a new segment created on an old route) or when a user crops an activity.
- New segments are backfilled with a batch job over historical activities in the segment's area — a Spark-style job, not the online path.
- Flag suspicious efforts (a "run" at 60 km/h) and let users report them; flagged efforts are removed from leaderboards.
What's expected at each level
- Mid-level: offline recording with a reliable upload, blob storage for raw files, asynchronous processing, and a basic feed.
- Senior: idempotent uploads, a spatial index to find candidate segments, sorted-set leaderboards with best-effort updates, and hybrid feed fan-out.
- Staff: segment matching accuracy and backfilling, re-processing pipelines, leaderboard filters at scale, privacy zones, cheating detection, and handling weekend peaks cost-effectively.