Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

System Design (High-Level Design)  Module 25 – Case studies: location, mobility and logistics

Design activity tracking like Strava: GPS ingestion

Design GPS activity uploads like Strava: requirements, estimates and a first design for repeat-safe uploads, clean tracks, streams and home privacy.

  • Intermediate
  • 25 minutes
  • Examples run with Python 3.14.8, Pyodide 314.0.7, Node.js 24.21.0 and quickjs 0.32.0
  • By MySmartCoPilot
Free intro, Pro: 5 of 25 minutes free
Free: About 670 uploads a second, and 570 TB a year to keep. With a pass: the other 15 sections and 3 interview answers, about 20 minutes.

What you will learn

  • Estimate peak uploads, yearly file and stream storage and feed map bandwidth for an activity tracker
  • Design an upload path for watches, bike computers and phones that never saves a repeated upload twice
  • Compute distance, moving time and climb from noisy GPS samples, and check them against a known route
  • Store activities as streams at several resolutions with a compact encoded line for maps
  • Protect home locations against an attacker who studies many public activities

Before you start

What you’ll learn · Before you start

What you will learn

  • Estimate peak uploads, yearly file and stream storage and feed map bandwidth for an activity tracker
  • Design an upload path for watches, bike computers and phones that never saves a repeated upload twice
  • Compute distance, moving time and climb from noisy GPS samples, and check them against a known route
  • Store activities as streams at several resolutions with a compact encoded line for maps
  • Protect home locations against an attacker who studies many public activities

Before you start

On this page

On a busy Sunday an activity tracker takes in about 670 uploads a second, each a run or ride from a watch, a bike computer or a phone, and keeps about 570 TB of new files and samples a year. Every upload must become a saved workout within seconds: a map, distance, moving time, climb and charts for followers.

The design rests on four ideas. Devices upload the finished file, which the service stores unchanged and processes later from a queue, so a retry or a second copy of the same run is recognised instead of saved twice. Processing is a pipeline over a time series: drop impossible jumps, smooth the track, then derive the statistics. Results are stored as columns of samples at several resolutions, with a short encoded line for each feed card’s map. And privacy is applied on the server before anything is shared, because a hidden start that is always the same circle still points at a home.

Not affiliated

Based on public information about Strava. MySmartCoPilot is not affiliated with Strava, and Strava’s real architecture may differ from what this lesson describes.

About 670 uploads a second, and 570 TB a year to keep

Settle these first, with the answers this design assumes:

  • Which devices? Watches and bike computers upload files; phone apps record and upload themselves. Strava’s API accepts FIT, TCX and GPX files, gzipped or not, and processes each upload asynchronously (Strava).
  • How big? 10 million uploads a day; a Sunday brings twice that, 12 % of it in the busiest hour.
  • How fast? A new activity appears within seconds; Strava’s API documentation gives a mean processing time under 2 seconds.
  • Live sharing? A recording can share its position with chosen contacts; Strava Beacon updates about every 15 seconds and needs mobile signal (Strava).
  • Privacy? Each activity has a visibility, and starts and ends near home are hidden; Strava hides the first and last 200 m of new activity maps by default (Strava).

So the service accepts uploads that may repeat, computes the statistics, keeps the original so that activities can be recomputed when the algorithms improve, serves maps and charts, and never shows a hidden part to anyone but the owner. Uploads may arrive late and in bursts after a race; nothing may be lost once accepted.

Quantity Working Result
Uploads, busiest hour 10 million × 2 × 12 % ÷ 3,600 s about 670 a second
Original files 10 million a day × 100 KB × 365 about 365 TB a year
Streams 3,600 samples × 12 bytes × 1.3 for two smaller resolutions about 56 KB an activity, 205 TB a year
Feed map thumbnails 300 million card views a day × 20 KB × 8 bits ÷ 86,400 s × 3 about 1.7 Gbit/s at the peak
Live positions 200,000 people sharing, one position every 15 s about 13,000 writes a second

Storage, not traffic, is the big number: about 570 TB a year that is read rarely and must never be lost. The upload rate itself is modest, but each upload starts several seconds of processing.

Exercise · Easy · Python

Size peak uploads, yearly file and stream storage, and feed map bandwidth

Write four functions in sizing.py for the estimate sheet of an activity-tracking service. An hour has 3,600 seconds, a day 86,400, a year 365 days; 1 KB is 1,000 bytes, 1 TB is 10¹² bytes and 1 Gbit is 10⁹ bits.

  • peak_uploads_per_second(daily_uploads, busiest_day_factor, busiest_hour_share): uploads a second in the busiest hour of the busiest day, which has busiest_day_factor times the usual day's uploads and puts busiest_hour_share of them into one hour.
  • yearly_file_storage_tb(daily_uploads, kb_per_file): a year of original files, kept as uploaded.
  • yearly_stream_storage_tb(daily_uploads, samples_per_activity, bytes_per_sample, resolutions_factor): a year of processed streams, where the smaller resolutions add resolutions_factor - 1 to the full one.
  • thumbnail_gbit_per_second(daily_views, kb_per_image, peak_factor): the bandwidth for map thumbnails in the feed at the busiest hour, which runs at peak_factor times the day's average.

The sample tests accept each figure within 10 %, as an estimate should.

Starter code · sizing.py

HOUR_SECONDS = 3_600
DAY_SECONDS = 86_400


def peak_uploads_per_second(daily_uploads, busiest_day_factor, busiest_hour_share):
    """Uploads a second in the busiest hour of the busiest day."""
    # Replace this line with your code.
    return 0


def yearly_file_storage_tb(daily_uploads, kb_per_file):
    """TB for a year of original files."""
    # Replace this line with your code.
    return 0


def yearly_stream_storage_tb(daily_uploads, samples_per_activity, bytes_per_sample, resolutions_factor):
    """TB for a year of processed streams at every resolution."""
    # Replace this line with your code.
    return 0


def thumbnail_gbit_per_second(daily_views, kb_per_image, peak_factor):
    """Gbit/s for feed map thumbnails at the busiest hour."""
    # Replace this line with your code.
    return 0
The sample tests · test_sizing.py
import math

from sizing import peak_uploads_per_second, thumbnail_gbit_per_second, yearly_file_storage_tb, yearly_stream_storage_tb


def near(actual, expected):
    """Within 10%, as an estimate should be."""
    return math.isclose(actual, expected, rel_tol=0.10)


def test_peak_uploads():
    """the busiest day's uploads, the busiest hour's share of them, per second"""
    assert near(peak_uploads_per_second(10_000_000, 2, 0.12), 667)
    assert near(peak_uploads_per_second(1_400_000, 1.5, 0.1), 58)


def test_file_storage():
    """a year of files at their uploaded size"""
    assert near(yearly_file_storage_tb(10_000_000, 100), 365)
    assert near(yearly_file_storage_tb(2_000_000, 60), 43.8)


def test_stream_storage():
    """samples times bytes, plus the smaller resolutions"""
    assert near(yearly_stream_storage_tb(10_000_000, 3_600, 12, 1.3), 205)
    assert near(yearly_stream_storage_tb(1_000_000, 1_800, 10, 1.2), 7.9)


def test_thumbnail_bandwidth():
    """the day's image bits per second, at the peak"""
    assert near(thumbnail_gbit_per_second(300_000_000, 20, 3), 1.67)
    assert near(thumbnail_gbit_per_second(50_000_000, 30, 2.5), 0.35)
A hint

Peak uploads: daily_uploads * busiest_day_factor * busiest_hour_share / 3_600. Storage multiplies the activities of a year by their bytes and divides by 10**12. Bandwidth spreads the day's bits over 86,400 seconds, multiplies by the peak factor and divides by 10**9.

The sample tests run on this device, in your browser (Pyodide): nothing is sent to mysmartcopilot.com. The first run downloads Python (about 13.5 MB), which is kept for the next runs. A check in your browser is feedback for you, not proof that the code is right for every input.

Checkpoint

Check the estimate

  1. Question 1 of 2 A usual day brings 10 million uploads, a Sunday twice that, and 12 % of the Sunday's uploads arrive in its busiest hour. About how many uploads a second is that?

    Type a number.

    A hint

    An hour has 3,600 seconds.

    Show the answer: Question 1 of 2

    Answer: 667 uploads a second (anything from 627 to 707 counts)

    20 million × 12 % = 2.4 million uploads in one hour, and 2,400,000 ÷ 3,600 ≈ 667 a second. The queue and the workers are sized from this number, not from the daily average of about 116 a second.

  2. Question 2 of 2 An activity has 3,600 samples of 12 bytes, and two smaller resolutions add 30 % to that. About how many KB of streams does one activity store?

    Type a number.

    Show the answer: Question 2 of 2

    Answer: 56 KB (anything from 52 to 60 counts)

    3,600 × 12 = 43,200 bytes, and × 1.3 = 56,160 bytes, about 56 KB. At 10 million activities a day that is about 205 TB a year, beside 365 TB of original files.

The first design: store first, process from a queue

Activity ingestion: an upload API, a file store, a queue and processing workers that write activities, streams and events.Watch, bike computeror phone appUpload APIchecks for repeatsLive sharinga position every 15 sFile storeoriginals, unchangedProcessing queueProcessing workersparse, clean, derive,hide private partsActivitiesstats and visibilityStream storeseveral resolutionsActivity eventsFeed, segments,map thumbnailsfinished filepositionsave firstupload id

The first complete design of activity ingestion

Text description of the diagram

The diagram shows the upload path and the live path.

  1. A watch, bike computer or phone app sends the finished recording to the upload API, which checks whether it repeats an earlier upload.
  2. The upload API saves the original file, unchanged, in the file store first, then puts an upload id on the processing queue.
  3. Processing workers take uploads from the queue, parse and clean the track, derive the statistics and hide the private parts.
  4. The workers write the activity's statistics and visibility, its streams at several resolutions, and an activity event.
  5. Activity events feed the consumers that build the feed, match segments and draw map thumbnails.
  6. Separately, a phone that shares its position live sends a position about every 15 seconds to the live sharing service.

The device sends the finished file to the upload API, which checks for repeats, saves the original in the file store and queues an upload id. Workers parse, clean and measure the track, hide the private parts, and write the activity’s statistics, its streams and an event that the feed, segment matching and map thumbnails consume. Live positions take a short path of their own. Follow one run through it:

Interactive · step by step One finished run through the first design Step through one upload from a watch to the feed, then a retry and a live position, and see which part answers.
One finished run through the first design, step 8 of 8. Showing Watch or phone, Upload API, File store (this run's original), Processing queue, Processing workers, Activities and streams (one new activity), Feed, segments, maps, Live sharing.finished filesave firstupload idactivity eventWatch or phoneUpload APIchecks for repeatsFile storethis run's originalProcessing queueProcessing workersparse, clean, deriveActivities and streamsone new activityFeed, segments, mapsLive sharinga position every 15 sposition

The drawing shows the whole scene; its 8 steps are listed below.

Every step as text
  1. A finished run. A watch finishes a run and uploads the whole file. The upload API first checks whether this upload repeats an earlier one.
  2. Saved first. The API stores the original, unchanged, in the file store before anything else, so nothing is lost once the upload is accepted.
  3. Queued. Then it puts an upload id on the processing queue and answers the device, which can show that the run is being processed.
  4. Processed. A worker takes the id from the queue, parses and cleans the track, derives the statistics and hides the private parts.
  5. Written. The worker writes the activity's statistics and visibility, its streams at several resolutions and an activity event.
  6. Read by others. The feed, segment matching and map thumbnails read the activity event, each on its own schedule.
  7. A retry. When an upload times out, the device sends the same file again. The API's check for repeats keeps it from becoming a second activity.
  8. A live position. A phone that shares its position while recording sends it about every 15 seconds on a short path of its own; the finished file still comes later as an upload.

The questions this design has to answer:

  • Repeat uploads: a retry sends the same bytes, a re-export different bytes, and a second device a second recording of the same run.
  • Cleaning a track: drop fixes that imply an impossible speed, then smooth what remains before measuring.
  • Moving time: pauses recorded by the device win; otherwise a speed threshold decides when the athlete rested.
  • Climb: a barometer’s readings, smoothed, or a terrain model; raw GPS altitude counts every wobble as climbing.
  • Streams and polylines: columns of samples at several resolutions, and an encoded line of points for maps.
  • Privacy zones: hide the start and end, with a hidden distance that varies, so repeated runs do not outline a home.
  • Reprocessing: keep every original and compute old activities again as a background job.

Checkpoint

Check the first design

  1. Question 1 of 2 In the first design, what does the upload API do with a file before it queues the upload?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: It checks for a repeat and saves the original, unchanged, in the file store

    Check for repeats, save the original first, queue an upload id: the API's three steps. Processing happens later, from the queue, so a burst after a race becomes queue depth instead of failed uploads.

  2. Question 2 of 2 Why does the upload API answer the device as soon as the upload id is queued, instead of when processing ends?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: Processing takes seconds and bursts after races; once the file is stored, the device only needs to know it was accepted

    Uploads may arrive late and in bursts after a race, and nothing may be lost once accepted. Storing first and answering at once lets the workers take the queue at their own pace.

A content hash catches only retries; overlapping times catch a second device Pro: opens with a Pro, Premium or Ultimate pass

In this part:

  • Dropping impossible jumps and smoothing within runs turns 14.15 km into 10.09
  • Climb from a barometer, not from GPS altitude
  • Streams as columns at three resolutions, and an encoded line for the feed
  • A fixed hidden circle gives a home away to within a metre
  • Reprocessing 5 billion activities takes about 96 cores for a month
  • Live positions every 15 seconds take a short path of their own
  • What gives way at 10× and 100×
  • Failure drills: nothing accepted is ever lost
  • Alternatives considered and rejected
  • What each level is expected to cover
  • A sample answer and a 45-minute self-mock
  • Practise: the whole design, one question at a time
  • Interview questions
    • An athlete records a morning run on a watch and on a phone app at the same time, and both upload. Another athlete's app retries one upload three times after timeouts. Design how the service recognises each case and what it does with each.
    • Runners in a city centre report that their 10 km runs show as 11 to 14 km, while runs in parks are right. What is happening, and how do you fix the distance without breaking the parks?
    • A security researcher shows that, from a user's public runs, they can find the user's home to within a few metres, although the user hid 200 m around it. Explain how, and redesign the privacy feature.
  • Key takeaways

3 diagrams · 5 runnable examples (Python) · 3 interview answers · about 54 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

Read a complete sample first: Design ride hailing like Uber or Ola: matching

References

Related tools

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.