Design file storage and sync
Save a file on your laptop and it appears on your phone a few seconds later; edit one slide in a 200 MB deck and only a sliver is uploaded. A sync service is a metadata problem dressed up as a storage problem: the bytes go to blob storage in content-addressed blocks, while a careful metadata service decides what every device should have. It follows the interview framework phase by phase.
1. The prompt
"Design Dropbox (or Google Drive): users keep a folder in sync across their devices, share files and folders with others, and can recover earlier versions."
Storing bytes is the easy half. The hard half is deciding, for every device, exactly what changed since it last looked; uploading only what changed; and never, ever losing or wrongly deleting someone's file when two devices disagree.
2. Frame the problem
How big are files, and how many users?
Files up to 50 GB; about 100M users with ~10 GB each.
An exabyte of logical data: deduplication and cheap storage matter. Big files must be split.
Do edits re-upload the whole file?
They shouldn't: a small edit should send a small upload.
Files become lists of blocks; only changed blocks move.
How fast must changes reach other devices?
Within seconds when they're online.
Devices need a push notification channel, not just polling.
Can devices work offline?
Yes, and sync when they reconnect.
Conflicts are inevitable; we need a policy that never loses data.
Sharing?
Files and folders, with other users and teams.
Shared folders become their own namespaces with permissions.
Version history and deleted files?
Yes, restorable for 30 days or more.
Old block lists are kept; garbage collection must respect retention.
Real-time co-editing inside documents?
No, that's a different product (Design D19).
We sync whole files, not keystrokes.
3. What it must do
- A user can upload, download, move, rename and delete files and folders from any device.
- Changes on one device appear on the user's other devices automatically.
- A user can share files and folders and restore earlier versions.
Not now:
- Co-editing inside documents: Design D19.
- Full-text search: an indexing job.
- Previews and thumbnails: a processing pipeline.
4. How well it must do it
| Target | Why this number |
|---|---|
| Never lose data: no acknowledged file is ever lost; no sync bug deletes a file the user didn't delete data safety | A sync bug that deletes files is the worst outcome. |
| Efficient: a 1 MB edit in a 200 MB file uploads a few MB, not 200 where it runs | Users are on laptops on hotel Wi-Fi. |
| Fast propagation: other online devices see a change within ~5 s speed | Sync should feel like one folder everywhere. |
| Consistent per namespace: every device converges on the same tree consistency | Two devices showing different folders erodes trust. |
| Private: users see only what they own or were shared security | It's people's personal and business files. |
5. Numbers that change the design
| Quantity | Working | → so we… |
|---|---|---|
| Storage | 100M users × ~10 GB average used | ~1 EB logical → so blob storage with content-addressed blocks, dedup and erasure coding |
| Metadata | 100M users × ~5,000 files × ~1 KB | ~500 TB of rows → so a sharded database, partitioned by namespace |
| Change rate | 20M active users a day × 50 file changes | 1B changes a day ≈ 12,000/s → so notifications must be cheap per device |
| Edit efficiency | A 1 MB edit inside a 200 MB file | With 4 MB blocks, upload ~4–8 MB instead of 200 MB |
6. The nouns
- Block: hash → bytes; immutable.
- File version: a path and its ordered block list.
- Namespace: a user's root, or a shared folder.
- Journal entry: one change in a namespace, with a sequence number.
- Device cursor: the last journal position a device has applied.
blocks sha256 → bytes in blob storage (+ reference counts for garbage collection)
files (namespace_id, path) → current version
versions version_id, namespace_id, path, size, block_list [sha256…], parent_version, author_device
journal (namespace_id, seq) → change {path, version_id | deleted}
devices device_id, user_id, cursor per namespace
7. The contract
REST for blocks and commits; a long-poll for change notifications. The user and device come from the session.
POST /v1/blocks/check {hashes: [...]} → {missing: [...]} (upload only these)
PUT /v1/blocks/{hash} (or a presigned URL) → 201
POST /v1/files/commit {path, parent_rev, blocks: [...]} → {rev, seq} | 409 conflict
GET /v1/changes?cursor=… → {entries: [...], cursor, has_more}
GET /v1/changes/wait?cursor=… → long-poll: returns when something changed
GET /v1/blocks/{hash} → bytes (or a presigned URL)
8. First working version
A file API, a metadata database, blob storage, and clients that poll.
8.1 Saving a file
The client uploads the whole file to blob storage and records its path and storage key in the database.
8.2 Syncing other devices
Every 30 seconds each device asks for the list of files and their modification times, compares it with its own copy, and downloads whatever is newer.
8.3 Sharing and versions
A share row grants another user access to a folder; old storage keys are kept as versions.
Parked for later (section 9 fixes each)
- A one-character edit re-uploads the whole file; an interrupted upload starts over.
- Comparing whole folder listings every 30 s, from every device, is expensive and slow.
- A crash mid-upload can leave a half-written file.
- Two devices edit the same file offline.
- Storing identical files and versions many times over.
- Deletes, garbage collection and shared-folder hot spots.
9. Make it hold up
9.1 Blocks, hashes and uploading only what changed efficient · never lose data
| Approach | Why | |
|---|---|---|
| ✗ Bad | Upload the whole file on every save | A one-character edit to a 2 GB video project re-sends 2 GB; an interrupted upload starts over. |
| ~ Good | Split files into fixed-size blocks, identified by their hash; upload only blocks the server doesn't have | In-place edits and appends send only touched blocks; identical blocks across versions are stored once. Dropbox describes this design with 4 MB blocks hashed with SHA-256. |
| ✓ Great | Add content-defined chunking where inserts are common, plus compression and resumable block uploads | Boundaries follow the content (a rolling hash), so inserting bytes near the start shifts only one chunk instead of every block after it. Try both in section 13. |
Because a block's name is the hash of its contents, blocks are immutable: they can be cached forever, uploaded in parallel and retried safely, and a file version is just a list of hashes. Deduplicating across users has a privacy cost (an attacker can learn whether someone stores a known file by seeing whether its upload is skipped), so many services deduplicate only within one user's data or encrypt per user.
9.2 The metadata service is the heart never lose data · consistent
- Commit is the atomic step: a file only changes when the client commits a new block list, after all blocks are uploaded. A crash mid-upload leaves orphan blocks (garbage collected later), never a half-written file.
- A journal per namespace: every commit appends an entry with an increasing sequence number. A device asks "what changed after my cursor?" and gets an ordered list, far cheaper than comparing whole folder trees.
- Strongly consistent and transactional within a namespace (a relational database sharded by namespace works well). Shared folders are their own namespace, mounted into each member's tree.
9.3 Telling devices something changed fast propagation
Polling every device every few seconds wastes requests at this scale. Devices hold a long-poll (or a WebSocket, or a mobile push) to a notification service, which only says "namespace N has changes". The device then pulls the journal from its cursor. The notification carries no data, so a lost notification costs only a delay (the device also checks periodically), never correctness.
9.4 Conflicts never lose data
Each commit includes the version it was based on (parent_rev); a commit based on an old version fails with a conflict. File sync tools don't merge binary content: the usual answer is to keep both, saving the loser as "report (conflicted copy).docx", and let the person choose. Deletes need care too: a stale device must never delete a file that another device has since edited.
9.5 Durability, versions, sharing and garbage collection never lose data · private
- Blocks live in blob storage replicated or erasure-coded across zones (Module 10). Old versions keep their block lists, so history is cheap: unchanged blocks are shared.
- Deleting a file only removes it from the tree; blocks are garbage collected when no version references them, after the retention period. Count references carefully: a GC bug is a data-loss bug.
- Access checks happen in the metadata service (who can read a namespace); block downloads use short-lived presigned URLs. Encrypt at rest; consider client-side encryption for sensitive products, at the cost of server-side features.
9.6 The sync engine on the client never lose data · consistent
Most data-loss bugs live in the client: reconciling the local disk, the last synced state and the remote journal, through renames, moves, crashes and full disks. Keep a local database of the last synced tree, apply remote changes and local changes as explicit, testable steps, and test with randomised simulations of crashes and concurrent edits. Dropbox wrote publicly about rewriting its sync engine for exactly these reasons.
9.7 The whole design after the deep dives
One edit on a laptop reaching a phone through the final design.
10. Interviewer follow-ups
A shared folder with 50,000 members at a big company.
One namespace gets heavy commit traffic and fan-out of notifications. Split very large shared trees into sub-namespaces, rate-limit commits per namespace, and batch notifications so devices pull once for many changes.
A user uploads a 50 GB file over a weak connection.
Blocks upload independently and resumably; the client tracks which are done. Only after all are present does it commit. Upload in parallel with a limit, and back off when the network is saturated.
A device was offline for a month.
It pulls the journal from its old cursor. If the journal for that range has been compacted away, it falls back to a full listing compared against its local database, which is slower but correct.
Ransomware encrypts every file on a laptop, and sync uploads them.
Version history lets the user restore the namespace to a point in time. Detect mass rewrites (thousands of files changed in minutes, high entropy) and offer a one-click restore.
Cut storage cost.
Deduplicate within accounts, compress blocks, erasure-code instead of triple replication, and move old versions and cold files to cheaper tiers.
LAN sync between devices in the same office.
Devices discover each other locally and fetch blocks by hash from a peer instead of the cloud. Hashes make this safe: a block is verified by its name.
Client-side encryption.
Encrypt blocks with keys the server never sees. It costs cross-user dedup, server-side previews and search, and makes key recovery a product problem.
11. When things break
| Failure | What users see | How we detect it | What limits the damage |
|---|---|---|---|
| Crash mid-upload | Upload resumes later | Missing blocks at commit | Commit only after all blocks; orphan blocks GC'd |
| Notification service down | Changes appear more slowly | Connection errors | Devices fall back to periodic journal checks |
| Metadata shard down | One namespace can't sync | Errors per shard | Replicated database with failover; clients retry |
| Concurrent offline edits | A "conflicted copy" appears | 409 on commit | Keep both; never overwrite silently |
| GC bug | Missing file content | Restore and audit checks | Retention period, reference-count audits, staged deletes |
12. Wrap-up and the clock
Trade-offs we made:
- Content-addressed blocks (dedup and resumability, at the cost of hashing and metadata per block).
- Commit-after-upload for atomicity.
- Per-namespace journals and cursors.
- Notifications without data.
- Conflicted copies instead of merging.
- Dedup within accounts for privacy.
What we'd watch:
- Change propagation time.
- Upload success and resume rates.
- Conflict rate.
- Journal lag per device.
- Storage per user after dedup.
- GC safety audits.
At 10×:
- More metadata shards.
- Split huge namespaces.
- Colder storage tiers.
A 45-minute interview
| 0–4 | Frame the problem (sizes, offline, sharing) |
| 4–9 | Must do, how well, numbers |
| 9–13 | Nouns and contract |
| 13–20 | First working version, parked list |
| 20–40 | Deep dives: blocks (9.1), metadata and journal (9.2), notifications (9.3), conflicts (9.4) |
| 40–45 | Follow-ups and wrap-up |
A 60-minute interview
| 0–5 | Frame the problem |
| 5–11 | Must do, how well, numbers |
| 11–16 | Nouns and contract |
| 16–24 | First working version |
| 24–50 | Deep dives 9.1–9.6 and the final picture |
| 50–60 | Follow-ups: big shared folders, ransomware, LAN sync; wrap-up |
| Level | What a strong answer shows |
|---|---|
| Mid-level | Files in blob storage, metadata in a database, uploads via presigned URLs, devices polling for changes. |
| Senior | Content-hashed blocks, upload only missing blocks, commit-after-upload, a per-namespace journal with cursors, long-poll notifications, conflict copies. |
| Staff+ | Chunking strategy and dedup privacy, garbage-collection safety, sharding by namespace and hot shared folders, client sync-engine correctness and testing, storage cost tiers. |
Common mistakes:
- Whole-file uploads.
- Diffing whole folder listings.
- Writing files before all blocks exist.
- Last-writer-wins on conflicts.
- Cross-user dedup without thinking about privacy.
- Ignoring the client's sync logic.
13. Try it, check yourself, read more
This is a real 64 KB file, chunked in your browser two ways: fixed 4 KB blocks, and content-defined chunks (a gear rolling hash, average about 4 KB). Make an edit and see which chunks must be uploaded again (orange).
How does a sync client avoid re-uploading a whole file after a small edit?
Why does content-defined chunking help with inserts?
A device wakes after a day offline. How does it catch up?
Two devices edited the same file offline. What does a sync service usually do?
| Resource | Why |
|---|---|
| Streaming file synchronization (Dropbox) | Blocks, SHA-256 blocklists and how uploads and downloads overlap. |
| Rewriting the heart of our sync engine (Dropbox) | Why the client's sync logic is the hard part, and how they made it testable. |
| FastCDC (USENIX ATC 2016) | A fast content-defined chunking algorithm using a gear hash, like the one in the lab. |