Android system design questions have a hidden premise, and candidates who miss it design something that works on their test phone and fails in every user's pocket.

The premise is that your app does not decide when it runs. The system kills processes it isn't using, defers work when the battery is low, cuts network access in deep sleep, and lets the user swipe your app away at any moment. A good Android design isn't one that avoids those events. It's one where each of them is boring.

Here's one prompt worked end to end, structured the way a strong answer is: by what will go wrong.

192
Chunks in one 1.5 GB video, at 8 MB each
5
Things that will kill the upload before it finishes
1
Rule: ask the server where you are, don't remember it

The prompt, and the scoping

"Design photo and video backup. A user selects 200 photos and a 1.5 GB video, taps upload, and puts the phone in their pocket."

Three questions, quickly:

"Should it respect a Wi-Fi-only setting? Does the user need to see progress, or is a final notification enough? And does the server support resumable uploads — can I send a file in pieces and ask how much arrived?"

Assume yes to all three, and say that the third one matters most: a server that only accepts whole files makes everything below much harder, and if it doesn't support resumable uploads, that's the first thing to negotiate with the backend team.

Do the arithmetic before designing anything

Two hundred photos at about 4 MB each is 800 MB. With the video, that's roughly 2.3 GB.

UplinkTime to upload everything
2 Mbps — a weak mobile connectionabout 2 hours 35 minutes
10 Mbps — decent home Wi-Fiabout 31 minutes

"So even in good conditions this outlives the app's process, and in bad conditions it outlives several of them. That's the design constraint. I'm not building an upload that tries to finish in one go — I'm building one that expects to be killed and picks up where it left off."

That sentence reframes the whole round, and it comes from thirty seconds of multiplication. Interviewers notice candidates who size the problem before they solve it.

The five things that will kill this upload

ThreatWhat actually happensThe design answer
Process deathThe system reclaims memory and your process is gone, mid-chunkState lives in a database, not in memory; the work is rescheduled by the system
The user swipes the app away, or rebootsAnything tied to the app's lifecycle stopsWork is scheduled with WorkManager, which persists it and survives reboots
The network changes or dropsWi-Fi to mobile in the lift, then nothingConstraints on the work, and chunked uploads that lose one chunk, not the file
Doze and battery restrictionsThe system defers background work and network accessRespect it — long work runs visibly in the foreground with a progress notification
Retries and lost acknowledgementsA chunk arrives but the response doesn'tThe server is the record of progress; ask it, don't trust a local counter

Laying the answer out as a threat table is itself a strong move. It shows the panel you started from failure, and it gives them five obvious places to dig — each of which you already have an answer for.

The design

A queue in the local database. One row per file: its content URI, a hash, the upload session ID from the server, how many bytes the server has confirmed, and a state — queued, uploading, done, failed. The UI's progress bar observes this table, so progress is correct even after the process has died and come back.

One worker, scheduled with WorkManager. It drains the queue file by file. It's enqueued as unique work, so tapping upload twice can't start two copies, with constraints — unmetered network if the user chose Wi-Fi only, battery not low — and exponential backoff when a request fails.

Run it in the foreground. A long transfer runs as foreground work with a progress notification. That's partly a system requirement and partly honest: the user can see what's using their data. Recent Android versions also require a declared foreground service type and cap how long data-sync work can run continuously — one more reason the upload has to be resumable rather than relying on finishing in one session.

Resumable, chunked uploads. Each file is sent in 8 MB pieces within a server-side upload session. The 1.5 GB video is 192 chunks. If the connection drops at 70%, a non-resumable upload throws away about a gigabyte. This one throws away at most 8 MB.

Deduplication by content hash. If the user selects the same photo again tomorrow, the hash matches and nothing is uploaded twice.

The detail that marks the senior answer: on resume, ask the server for its offset — don't use your own. The local database might say chunk 140 was sent, because the bytes went out. But if the acknowledgement was lost, the server may only have 139. Resuming from your own record skips a chunk and corrupts the file; resuming from the server's record costs one extra request. Candidates who store progress locally and trust it have designed a bug that only appears on bad networks, which is exactly where uploads happen.

The trade-offs worth naming

One worker draining a queue, or one worker per file? A worker per file is simpler to reason about and lets files upload in parallel, but 201 scheduled jobs are harder to cancel as a group, and parallel uploads on a weak connection just split the same bandwidth. I'd drain a queue, one file at a time, and say that I'd revisit it if the server were slow per request rather than limited by bandwidth.

Chunk size. Smaller chunks lose less on failure and cost more requests; larger chunks are efficient on good networks and painful on bad ones. 8 MB is a defensible middle, and the honest addition is that I'd tune it from real failure data rather than defend the number.

Wi-Fi only by default? It protects people's data plans and makes completion slower and less predictable. I'd default to it for video and not for photos, and I'd want product to make that call with me rather than bury it in a setting.

The follow-ups

"The user deletes a photo from the gallery mid-upload." — "The worker reads the file through its content URI when it needs each chunk, so the read fails. I'd mark that item failed with a clear reason and move on to the next file — the one thing I wouldn't do is let one missing file stop the other 200."

"How would you test this?" — "Kill the process while it's uploading — background the app and kill it from the command line — and check it resumes from the server's offset, not from zero. Throttle the network and drop it mid-chunk. And test the unique-work behaviour by tapping upload twice, because duplicate jobs are the bug that ships most often."

"Where does the progress bar get its number after the process restarts?" — "From the queue table, which the worker updates as the server confirms chunks. The screen doesn't need to know whether the upload is running right now — it just observes the database."

What works on the test phone

Upload from the ViewModel

“When the user taps upload, I launch a coroutine in the ViewModel that loops over the files and uploads each one, updating a progress state.”

Fine on a desk with Wi-Fi and the app open. It dies with the screen, restarts from zero after process death, and has nothing to say about the user's pocket — the only scenario the prompt actually described.

What works in the pocket

Persisted queue, scheduled worker

“State in the database, work scheduled by the system, chunks resumable from the server's offset. Each of the five threats ends in a retry, not a restart.”

The design assumes the process will die and makes that uneventful. It also gives the panel a map of the failure cases, which is the conversation they wanted to have.

Saying it out loud

Lead with the threat table. Five rows, one sentence each, before any component names — "here's what will go wrong, and here's what I'd build so each one is boring." It's easier to follow than a component tour and much harder to argue with, because every component arrives already justified.

The phrase worth having fluent is the arithmetic one: "two and a half hours on a weak connection, so this outlives the process." It's short, it's concrete, and it's the moment the interviewer realises you're designing for the real phone.

Offline-first sync with conflicts is a separate classic, and it's question 16 in Android Developer Interview Questions. The iOS round asks a different question — a chat client — with the same underlying principle of a local source of truth: the iOS system design interview.

Practical target: write the five-row threat table for a feature you've actually shipped, then fill in the last column honestly. Any row where the answer is "that probably won't happen" is the hole a panel will find — and it's usually the one your real users have already found too.