Mobile Design Rounds
Case studyAndroidiOS6 min read

Design a public transport app (Android and iOS): free teaser

Last reviewed . Drafted with AI assistance. Checked against official documentation where noted (how this is checked). Part of: The framework · Staff-level guide.

Free teaser. This page is the free start of the case: the prompt, the scope and the first two decisions. The full case is planned for the study pack, which does not exist yet. See the pack waitlist.

How to read this. This page is the free start of the case. You get the prompt, the scope and the first two decisions in full. The other parts are a short outline. The follow-up questions are my reconstruction of challenges that public prep material reports: offline, old data, battery, rollout. They are not transcripts of real rounds.

TL;DR. My answer: the hard part is not the timetable download. It is telling the user, on every row, how much the app knows. The timetable is one file that the app swaps whole. Realtime data is a short layer on top, and it shows its age. The server decides which disruption is worth a push.

1. Scope and constraints (minutes 0 to 4)

In short: Promise stops, scheduled departures and saved routes offline. Keep trip planning on the server.

A prompt I would expect: "Design an app that shows when my bus leaves, even with no signal." I ask five questions, with a default for each:

  • One city or many? One city, one agency feed.
  • Trip planning? Yes, on the server.
  • Realtime data? Usually, so the app must work without it.
  • Offline? Nearby stops, scheduled departures, saved routes.
  • Location? Not required.
In scopeOut of scopeParked until you ask
Stop boards (lists of next departures)Tickets and paymentPlanning with no server
Saved routesWalking directionsA live map of vehicles
Live, scheduled or stale labelsReports from ridersA widget or a second phone
Disruption alerts, tunnelsMany cities or agencies

The phone is not one box. It does four things to this design:

What the phone doesRequirement at riskDeep dive
Stops the app processNo half-written timetableData (2), structure (3)
Delays background workA current timetableStructure (3), alerts (4.2)
Loses signal, keeps a dead linkA time never liesThe honest row (4.1)
Has a battery and data planSmall downloads, little locationNumbers (2), alerts (4.2)

Your decision. What do you promise a user with no signal? "Plan any trip", or "the next departures for stops and routes you know"?

First idea, and why it fails. Promise full planning offline, because the tunnel is the main use. It fails on cost. The phone needs a route engine and a bigger timetable file.

What I would say.

Say this. "Offline means the stops and routes you already know. Trip planning stays on the server. Tickets, walking directions and rider reports are out. A live map, a widget and many cities wait until you ask."

The follow-up question I expect:

"Some apps plan trips on the phone."

That is a valid later step. I would want a measure first.

2. Data and contracts (minutes 4 to 14)

In short: The server builds one timetable file. The app swaps it whole. Realtime data stays in memory with its age.

Your decision. Where does the timetable live? Does the phone parse the agency zip, ask the server for every query, or download one compiled file?

First idea, and why it fails. Download the GTFS zip and read it into the app. GTFS (General Transit Feed Specification) is the standard set of timetable text files. Reading a big city's stop times blocks the app. If Android stops the process midway, the database holds half a timetable.

What I would say. The server compiles one SQLite file (a database in a single file). I call it the pack. The app downloads it, checks its hash and schema version, opens it as a new file, and then moves one pointer. Saved routes live in a second database that a swap never touches. I reject patches, because the numbers below do not need them.

The follow-up question I expect:

"A new pack arrives while the user reads a board."

Readers keep the old file. The next query uses the new one.

Servercompiles one SQLite file: the packdownloadCheck the filehash and schema versionthen move one pointerOld packopen readers keep itNew packnext query uses itSaved routesa second database; a swap never touches it
The server builds one timetable file. The app checks it, opens it as a new file and moves one pointer, so readers never see a half-written timetable.

The follow-up question I expect:

"What about a trip at 25:10?"

GTFS writes times after midnight as more than 24:00:00, on the service day that started earlier. The spec measures them from noon minus 12 hours of that day (GTFS reference), in the agency time zone. On the day the clock changes, adding 86,400 seconds is wrong by one hour. That last point is my reasoning.

The contract has four calls:

  • Manifest: version, valid dates, schema version, hash, size.
  • Realtime slice for one stop. The raw feed covers the whole network, so the server cuts a slice.
  • Journey request, answered by the server.
  • Alerts, matched to saved routes by the server.

These are my own assumptions, not measurements:

AssumptionArithmeticWhat it decides
2 million stop time rows at 28 bytes. A zip is 3 times smaller2,000,000 x 28 B = 56 MB. Download 19 MB. Peak disk 3 x 56 = 168 MB. Daily download 19 x 30 = 570 MB a month. Weekly 82 MBDownload on an unmetered network, only when the version changes, with 200 MB free. No patches
Feed age limit 90 s. The board polls every 30 s90 + 30 = 120 sA live time can be 2 minutes old. A row is stale after 180 s (1.5 times 120)
Walking 1.4 m/s. A geofence reports after 3 minutes on average, 6 at worst1.4 x 180 = 252 m. 1.4 x 360 = 504 mA geofence for prefetch needs a radius of 500 m or more
Timetable packscheduled times,works offlineLive layerone stop's slice,kept in memoryScheduledno live dataLiveslice is freshStaleover 180 s oldEach row carries its label.
The timetable pack gives scheduled times. A live layer sits on top and shows its age, so every row says how much the app knows.

3 to 5 in short: what the full case covers

In short: Three more decisions follow. Here is each one, and the first idea that fails.

  • 3. Client structure (minutes 14 to 22). Who owns the timetable and its updates? The first idea is to let each screen download when it opens. That starts two downloads and can leave a temporary file with no owner.
  • 4.1 The honest row (minutes 22 to 31). What does a row say when live data is missing, late or old? The first idea is to treat a missing update as on time. In a tunnel the row would still say, for example, "2 min".
  • 4.2 Who wakes the phone (minutes 31 to 40). Does the phone watch for a disruption, or does the server tell it? The first idea is to poll in the background and follow the user with location. Android limits both.
  • 5. Break it, ship it, roll it back (minutes 40 to 45). What does the user see when a download stops, the clock is wrong or a release is bad? The first idea is to ship a fix and wait. A store lets you stop a rollout, not recall it.

The full case also has six follow-up questions that each change one constraint. It ends with what changes at mid, senior and staff level.

Get the full case

In short: The full case will be part of the study pack. The pack does not exist yet. You can join the waitlist.

The full case, with the follow-up questions, the failure table, the levels and the build guide, will be part of the study pack. The pack does not exist yet. You can join the waitlist: /pack/.

This page stays free at this address. If the pack is dropped, or six months after it launches, the full text goes live here.

Verification notes (last reviewed 13 Oct 2026)

this is the public teaser of the transport case. It holds the full text of sections 1 and 2 from the private full case, shortened to the outline for sections 3 to 5. The private file is the source and is not changed. Verified: I read the GTFS and Android pages behind sections 1 and 2 on 2026-10-14, as recorded in the notes of the full case. The 25:10 rule comes from the GTFS Schedule reference, which the fetch tool read only in part. The numbers in section 2 are my assumptions, and I recomputed the arithmetic: 2,000,000 x 28 B is 56 MB, 3 x 56 is 168 MB, 19 x 30 is 570 MB, 90 + 30 is 120 s, 1.5 x 120 is 180 s, and 1.4 x 360 is 504 m. Not verified on this page: the outline claims for sections 3 to 5 are not linked, because the full case holds the sources. The claim that a store cannot recall a build, and the Android limits on background polling and location, were checked in the full case but are not re-checked here. The pushes and follow-ups are my reconstruction, not transcripts. No price or launch date is claimed. The 6 month rule comes from MONETIZATION-PLAN.md and is not yet confirmed by the owner. The Basis, levels and failure table are not on this page. The build guide link is left out. Source-check pass, 2026-10-14: I added the GTFS reference link for the noon-minus-12-hours rule and marked an example.

Questions or corrections?

Add them to the discussion thread for this case on GitHub. To post, you need a free GitHub account. What you write there is public and follows GitHub's terms. Good answers are added to this page.

Keep reading

Get the next case study when it is published.

You get an email when a new case study is published, and nothing else.

You can unsubscribe at any time. Privacy.