Design Dropbox: the file storage and sync system design walkthrough
The request to design Dropbox sounds like one line — put my files in the cloud and keep them the same on every device — and that one line hides the whole interview. Picture it as upload and download and you reach for a form, a blob store and a save button; picture it as file storage and sync where a 1 GB video has one byte changed, a laptop and a phone must both end up identical, and two machines edited the same file while both were offline, and it becomes one of the more deceptively deep problems in distributed systems. This is one of the most revealing file sync system design prompts because it is not about storing bytes first — it is about sync efficiency and consistency: moving only what changed, converging every device on the same file, and never silently losing an edit.
The round turns on one structural insight. "Store my files and sync them" hides the fact that a file is the wrong unit of work — if you treat it as one opaque blob, a one-byte edit forces a gigabyte back over the wire and every save fights every other save. The reframe that unlocks the whole design is to stop treating a file as one blob and treat it as an ordered list of content-addressed chunks: split each file into fixed-size pieces, identify each piece by the hash of its contents, and now sync is just "which chunk hashes changed." Get that reframe — a file is a list of chunk hashes, so you move chunks not files, store each unique chunk once, and let a small metadata service be the source of truth while the bytes live in a blob store — and the rest of the system design interview is deciding how a device learns something changed, how it pulls only what it is missing, and what happens when two devices disagree. This walkthrough follows the path a strong candidate draws on the whiteboard — requirements, estimate, API and data model, high-level design, then the deep dives — naming the follow-up the interviewer uses at each step to tell a memorised answer from real understanding.
How the file-sync round is actually run
A file storage and sync round runs about 45 minutes on a shared canvas — you draw the boxes and arrows while you talk, and the interviewer interrupts. It is a favourite because the one-line problem opens onto deep water fast: the moment you draw "upload the file, download the file," the question becomes what happens when a huge file barely changes and how do two devices stay identical without clobbering each other, and every answer after that compounds from those two. The clock is a touch tighter than the hour-long rounds, so you have to spend your minutes on chunking, the sync flow and conflicts, not on the sharing-permissions screen.
The interviewer rarely lets "just upload it to storage" stand. "You change one byte in the middle of a 1 GB file, and your phone is asleep in your pocket — what actually gets uploaded, how does your phone find out, and what does it download when it wakes up?" That question — efficient, consistent sync of a large file across devices — is the one a copied answer can never rehearse, and the one this round is really testing.
The rounds here are modelled on how the loop runs, not scraped from any candidate's recall. What matters for prep is the shape: a blank canvas, a tighter clock, and a follow-up after every decision you make.
The five-step framework for any HLD round
Every high-level design answer fits the same spine. Say it out loud at the start so the interviewer knows you have a plan, then walk it:
- Clarify requirements — functional and non-functional, and what you are explicitly not building.
- Estimate — file sizes, edit rates and device counts versus total bytes stored, to justify every later choice.
- High-level design — the boxes and arrows: the clients, the metadata service, the notification service, and the blob store the bytes actually live in.
- Deep dive — the two or three decisions that actually decide this system.
- Follow-ups — bottlenecks, failure modes, and what you would change at ten times the scale.
What the debrief measures
The score is not "right or wrong design." A good HLD debrief reads on four dimensions, and every move below maps to one: problem solving (did you reframe a file as an ordered list of content-addressed chunks and reach chunking, dedup and delta sync), communication (did you say why a one-byte edit must not re-upload a gigabyte, and why bytes go straight to blob storage while app servers touch only metadata, each in a sentence), technical execution (fixed-size hashed chunks, a metadata service holding the chunk list and version sharded by user, a notification service, presigned direct-to-blob transfer, conflicted copies), and time management (did you spend your 45 minutes on sync efficiency and conflicts, not on previews and thumbnails). Reading a walkthrough teaches the design; it does not teach you to deliver it on the clock while someone pushes back.
Step 1 — Requirements: what a file storage and sync service must do
Spend the first few minutes here. Jumping to boxes before you have scoped the problem is the fastest way to design the wrong system.
| Type | Requirement |
|---|---|
| Functional | Users upload and download files; a file is kept identical across all of a user's devices; edits sync automatically and quickly; files can be shared with other users; version history is kept; the client works offline and reconciles on reconnect. |
| Functional (optional) | Very large files, selective sync (only some folders on a device), previews/thumbnails, trash and restore — call these out, then scope most of them for later. |
| Non-functional | Sync efficiency — never re-send data that did not change; durability — a stored file is never lost; consistency — every device converges on the same bytes and no edit is silently dropped; high availability; scale to enormous total bytes and many devices per user. |
The sentence that earns points: "This is a sync-efficiency and consistency problem, not upload/download CRUD — if I treat a file as one blob, a one-byte edit costs a gigabyte, so I'll treat each file as an ordered list of content-addressed chunks and sync only the chunks that changed." Say that and you have framed the entire design before drawing a box.
Step 2 — Capacity estimation (back of the envelope)
Estimation here is about proving one thing: the bytes are enormous but mostly unchanged between versions, and the metadata is tiny — so the design must move deltas, not files, and keep the two apart.
| Quantity | Assumption | Result |
|---|---|---|
| Files and devices per user | many files, a handful of devices | a bounded fan-out per account — the unit of work is one user's files across their devices, not the whole fleet |
| File size and edit rate | files range from kilobytes to many gigabytes; edits usually touch a small fraction of a large file | most bytes are unchanged on edit — a whole-file re-upload would waste almost all of it, so delta sync is the win |
| Total bytes stored | petabyte-scale and growing | bytes belong in a blob/object store that scales independently, not in your application database |
| Metadata rows | one small record per file plus its chunk list | metadata is small and consistent — shard by user id and keep it off the data path |
Two conclusions fall out, and you should say both out loud. First, there are two planes: a small, consistent metadata plane (what a file is — its chunk list and version) and a huge, content-addressed data plane (the chunk bytes themselves). Keep them separate — app servers manage metadata, bytes go straight to the blob store. Second, because most of a large file is unchanged between versions, the only thing worth syncing is the delta, which is exactly what content-addressed chunking gives you for free. You have now pointed the interview at the real deep dives — chunking and delta sync, the notification/sync flow, and conflicts — before anyone asked.
Step 3 — API and data model
The API is deliberately small: a client asks what the file is now, uploads only the chunks that are new, and subscribes for changes.
| Operation | Shape |
|---|---|
get_file_metadata(file_id) |
returns the file's current version and its ordered list of chunk hashes. |
has_chunks(hash[]) |
the dedup check — tells the client which of these chunk hashes the store already has, so it uploads only the rest. |
put_chunk(hash, bytes) |
uploads one chunk's bytes directly to the blob store via a presigned URL. |
commit_file(file_id, chunk_hash[], base_version) |
records a new file version as a new ordered chunk-hash list, against the version the client last saw. |
get_chunk(hash) |
downloads one chunk (served via CDN) to reassemble the file. |
subscribe_changes(user_id) |
opens a long-poll/websocket so the device is told when anything under the account changes. |
The data model is thin, and the split between metadata and bytes is the whole point:
| Entity | Notes |
|---|---|
file |
file_id, owner, name, version, ordered chunk_hash[] — the metadata source of truth for what the file is; sharded by user id. |
chunk |
hash → the chunk's bytes in the blob store; content-addressed so an identical chunk is stored once and referenced many times (deduplication). |
device |
(user_id, device_id, last_synced_version) — what each device has already caught up to, so catch-up is a cheap diff. |
version |
the prior chunk_hash[] lists per file — history, so nothing is ever truly overwritten. |
Step 4 — High-level design: metadata plane, data plane and notifications
Now the boxes. The design is defined by a line drawn through the middle of it — metadata on one plane, bytes on another:
- Metadata service: the source of truth for what each file is — its version and ordered chunk-hash list — sharded by user id. It is small, consistent, and never handles file bytes.
- Notification service: holds long-lived connections (long-poll or websocket) and tells a device "something under your account changed" so it knows to sync. It pushes a signal, not the data.
- Blob / object store: where the chunk bytes actually live, content-addressed by hash, scaling independently as petabytes. Clients read and write chunks directly here via presigned URLs — the app servers never sit in the byte path.
- CDN: downloads of chunks are served through a CDN so bytes come from near the user, not from one region.
-
Chunk index / dedup: a lookup of hashes the store already has, so
has_chunkscan tell a client to skip anything already stored.
Say explicitly that app servers never touch file bytes: the client chunks and hashes locally, the metadata service tells it which chunks are new, and the bytes flow straight to the blob store via a presigned URL. Naming that split — a light control plane for metadata and notifications, a heavy data plane that scales with the object store and CDN — is a signal the interviewer is listening for.
Deep dive: chunking, deduplication and delta sync
This is the decision the whole round is really about. The follow-up is the one-byte edit: you change one byte in the middle of a 1 GB file — what actually gets uploaded?
Start by killing the naive answer out loud: re-uploading the whole file is unacceptable. A one-byte change does not mean a gigabyte of new data, so sending a gigabyte wastes bandwidth, battery and time. The fix is to make the file addressable in pieces:
- Chunk the file: split it into fixed-size chunks (~4 MB is a reasonable default). Chunking is what makes a partial change a small change.
- Hash each chunk: compute a hash of each chunk's contents (e.g. SHA-256). The hash is the chunk's identity — this is content addressing. Two chunks with the same bytes have the same hash, anywhere, for anyone.
- The file is a list of hashes: metadata stores the file as an ordered list of chunk hashes plus a version. That list, not the bytes, is what the control plane reasons about.
- Deduplicate: because chunks are addressed by content, an identical chunk is stored once in the blob store and referenced by every file and user that contains it — saving both storage and upload bandwidth.
- Delta sync: editing a file re-chunks it; only the chunks whose hash changed are uploaded or downloaded. Change one byte in a 1 GB file and you re-chunk, find that one ~4 MB chunk has a new hash, upload that single chunk, and commit a new hash list. One chunk moves, not a gigabyte.
The point to state plainly: content addressing turns sync into set arithmetic. "What do I need to upload?" is just "which of my chunk hashes does the store not already have?", and "what do I need to download?" is "which hashes in the new version am I missing?" The whole efficiency of the system falls out of identifying chunks by their contents.
"You change one byte in the middle of a 1 GB file — what actually gets uploaded?" The client re-chunks the file into fixed-size pieces and hashes each one. Only the single ~4 MB chunk containing that byte gets a new hash; every other chunk's hash is unchanged and already in the store. So the client uploads that one chunk and commits a new version that is just a new ordered list of chunk hashes — one chunk on the wire, not a gigabyte. Identical chunks are stored once and reused, so even that upload is skipped if the store already has it.
System Design
HLD · Storage & files · 45 min
Design Dropbox out loud, with follow-ups
Deep dive: metadata, change notification and the sync flow
Chunking decides what moves; this decides how a device even learns it should move anything, and how the two planes work together. The follow-up is about discovery: how does a second device even know a file changed?
The answer is a notification service plus a diff against the metadata, with bytes pulled straight from the blob store:
- Metadata is the source of truth for state: the metadata service holds each file's current version and chunk-hash list, per user. It knows what the file is; the blob store knows the bytes.
- A change notification wakes the device: when a file changes, the metadata is updated and the notification service pushes a lightweight signal — over long-poll or websocket — to the user's other devices: "something under your account changed." It sends the nudge, not the data.
-
The device pulls the delta: on that signal the device calls
get_file_metadata, gets the new ordered chunk-hash list, and diffs it against its local list. The chunks whose hashes it does not already have are exactly what it must fetch. - Download only the missing chunks: it fetches just those chunks from the blob store (via CDN), then reassembles the file in the order the hash list specifies. Unchanged chunks are already on disk and are never re-downloaded.
-
Upload mirrors it: to push a local edit, the client re-chunks, calls
has_chunksto learn which hashes are new, uploads only those directly to the blob store, thencommit_filerecords the new version. Offline edits are buffered and run the same diff on reconnect.
The point to state plainly: the notification carries no content — it only triggers a pull, and the pull is a cheap diff of two hash lists. This keeps the control plane tiny and lets the data plane (blob store + CDN) carry the weight. A device that was asleep for a week catches up with exactly the same diff as one that missed a single edit.
"How does a second device even know a file changed?" It holds a long-poll or websocket connection to a notification service. When a file under the account changes, that service pushes a small 'something changed' signal — never the bytes. The device then pulls the updated metadata, diffs the new ordered chunk-hash list against what it has locally, and downloads only the chunks it is missing from the blob store through the CDN, reassembling the file from the hash list. A week offline or one edit behind, it is the same cheap diff.
System Design
HLD · Storage & files · 45 min
Defend the metadata/data-plane split out loud
Deep dive: scale, consistency and conflicts
Chunking and the sync flow keep a well-behaved account consistent; this decides what happens at petabyte scale and when two devices genuinely disagree. The follow-up is the collision: two of your devices edit the same file while both are offline — what happens when they reconnect?
The scale answers come from the two-plane split; the consistency answer is to detect divergence and keep both copies:
- Bytes never touch the app servers: uploads and downloads go directly to the blob store via presigned URLs, so the control plane stays small and the data plane scales with the object store and CDN. App servers move metadata, not gigabytes.
- Shard metadata by user id: a user's files and devices are the natural unit of coordination; different users are independent, so metadata scales horizontally and a hot account is one shard's load.
- Detect conflicts by version lineage: every commit is made against a base version. If two devices both edited from version N while offline, on reconnect the server sees two different children of the same version — that is a conflict, not a valid linear update.
-
Never silently overwrite — make a conflicted copy: rather than letting one device clobber the other, the server keeps both: the file and a
(Device B's conflicted copy). Both users' work survives, and the person decides how to merge. Last-write-wins would silently destroy an edit, which is unacceptable. - Versioning and resumable uploads: because every version is just a stored chunk-hash list, history is free and restores are cheap; and because uploads are chunked, a dropped connection resumes at the next un-acked chunk instead of restarting the file.
The point to state plainly: consistency here means convergence without loss. A single writer's change propagates by notify-then-pull; concurrent writers are detected by divergent version lineage and resolved by preserving both, never by picking a winner that deletes work. The chunk model makes all of it — dedup, delta, history, resumable uploads — the same mechanism seen from different angles.
"Two of your devices edit the same file while both are offline — what happens when they reconnect?" Each committed its edit against the same base version, so the server sees two different children of that version — a genuine conflict. It never silently overwrites: it keeps the original and writes the other as a conflicted copy named for the device, so neither person's work is lost, and versioning retains the history. A normal single-writer change, by contrast, is just notify-then-pull-the-delta.
The follow-ups that decide the file-sync round
Scan the probes from each deep dive and they rhyme. Master these five and you are ready for a version of the question that is not drawn exactly like this one.
- "Why not just re-upload the file when it changes?" → Because a one-byte edit does not mean a gigabyte of new data. Chunk the file into fixed-size pieces, hash each (content addressing), and upload only the chunk whose hash changed — one ~4 MB chunk, not the whole file. (problem solving)
- "How do identical files or chunks avoid being stored twice?" → Chunks are addressed by the hash of their contents, so an identical chunk has the same hash everywhere and is stored once and referenced many times — deduplication across versions, files and users. (technical execution)
- "How does another device know a file changed, and what does it fetch?" → A notification service pushes a lightweight 'something changed' signal over long-poll/websocket; the device pulls the new chunk-hash list, diffs it against its local list, and downloads only the missing chunks from the blob store via CDN. (communication)
- "Why don't the app servers handle the file bytes?" → To keep the control plane light and the data plane scalable: metadata (small, consistent, sharded by user) goes through app servers, but bytes go directly to the blob store via presigned URLs and are served through a CDN. (technical execution)
- "Two devices edit offline — who wins?" → Neither is silently overwritten. Both committed against the same base version, so the server detects divergent lineage and keeps both as a conflicted copy, with versioning preserving history. (problem solving)
Each maps to a debrief dimension. Reading this list teaches you the answers; it does not teach you to deliver them while someone interrupts — that only comes from saying them out loud.
How to prepare: a one-week plan
You do not need fifty systems. You need this one cold and the framework automatic, so the next "design X" is just new nouns in the same five steps.
- Days 1–2: Learn the five-step framework until it is reflex. Do the file-sync service end to end on paper — requirements, estimate, API, data model, and the metadata-service/notification-service/blob-store boxes.
- Days 3–4: Drill chunking: fixed-size chunks, content-addressed hashes, a file as an ordered hash list, deduplication, and delta sync. Say the one-byte-in-a-gigabyte answer in a single sentence.
- Days 5–6: Own the sync flow and conflicts — notify-then-pull, diff the hash lists, download only missing chunks, and the conflicted-copy resolution for offline edits. Answer each of the five follow-ups out loud.
- Day 7: A full timed 45-minute round, narrating the whole way, with someone (or something) interrupting. This is the rep that counts.
This post is the deep walkthrough behind one row of the broader Top 25 system design interview questions — once the file-sync service is automatic, the same spine carries the rest of the list.
Frequently asked questions
How do I design Dropbox step by step?
Follow the five-step HLD framework. Clarify requirements (upload/download files, keep them identical across a user's devices, sync edits automatically, share with others, version history, work offline then reconcile — with the hard rules of sync efficiency, durability, and convergence with no silent loss); estimate to show that bytes are huge but mostly unchanged on edit while metadata is tiny, which forces delta sync and a two-plane split; define the small API (get_file_metadata, has_chunks, put_chunk, commit_file against a base version, get_chunk, subscribe_changes) over a file/chunk/device/version model; draw the design (clients to a metadata service sharded by user id and a notification service, with chunk bytes flowing directly to a blob store via presigned URLs and served through a CDN); then deep-dive chunking + dedup + delta sync, the notification/sync flow, and scale + conflicts. Finish on the follow-ups about one-byte edits and offline collisions.
How does file sync avoid re-uploading the whole file when it barely changes?
By splitting each file into fixed-size chunks (around 4 MB) and identifying each chunk by a hash of its contents. The file is stored in metadata as an ordered list of those chunk hashes. When you edit the file, the client re-chunks it and compares hashes: only the chunks whose hash changed are new, so only those are uploaded. A one-byte change in a 1 GB file produces exactly one new chunk hash, so roughly one 4 MB chunk moves instead of a gigabyte. This is called delta sync, and it falls directly out of addressing chunks by their content.
What is content-addressable chunking and deduplication?
Content-addressable means a chunk's identity is the hash of its bytes, not a name or location. Two chunks with identical contents produce an identical hash anywhere in the system, for any user. Deduplication uses that property: an identical chunk is stored once in the blob store and simply referenced by every file, version or user that contains it, instead of being stored again. So the same attachment synced by a hundred people, or the unchanged 99% of an edited file, costs storage and bandwidth only once. The file itself is just an ordered list of these content hashes.
How does another device know a file changed, and what does it download?
Each device holds a long-poll or websocket connection to a notification service. When a file under the account changes, the metadata is updated and the notification service pushes a small "something changed" signal — never the file bytes. The device then pulls the file's new ordered chunk-hash list from the metadata service, diffs it against the list it already has locally, and downloads only the chunks it is missing from the blob store, served through a CDN, before reassembling the file in order. Whether the device was one edit behind or a week offline, it is the same cheap diff of two hash lists.
How are conflicts and offline edits handled?
Every change is committed against the version the client last saw. For a single writer, that is a clean linear update that simply notifies the other devices to pull the delta. When two devices edit the same file while both are offline, each commits against the same base version, so on reconnect the server sees two divergent children of that version — a genuine conflict. Rather than silently overwrite one with the other (which would destroy real work), it keeps both: the original and a conflicted copy named for the second device, leaving the person to merge them. Version history is retained throughout, and because uploads are chunked, a dropped connection resumes at the next chunk rather than restarting.
How do I practise a system design interview out loud?
Reading a walkthrough is not the same as defending a design while someone interrupts. On Zynter, the HLD track runs live voice rounds modelled on how real system design loops run — you talk through a shared design on the canvas, and a voice interviewer pushes with the follow-ups above one at a time, then gives you a debrief on problem solving, communication, technical execution, and time management. The score is a practice signal, not a hiring decision.
Practise it on Zynter
You can read what chunking and delta sync mean. The round is won on reasoning through sync efficiency out loud.
Pick the File Storage and Sync round, draw the metadata-plane, data-plane and notification design on the canvas, and defend it out loud to a voice interviewer that keeps asking "you change one byte in a 1 GB file — what moves?" and "two devices edit offline — who wins?" — then read the debrief. Zynter.ai is on-demand mock interviews modelled on real company loops, with 150+ rounds across 20 companies including the HLD system design track (Sep 2026).
Practise the File Storage and Sync round →
Browse all interview tracks




Top comments (0)