System design interview guide
Dropbox System Design
A user edits a 500 MB deck on a train while another laptop saves a different copy from the office. Hotel Wi-Fi drops halfway through a large upload. A shared folder sends thousands of change notifications at once. A Dropbox-style system must keep files durable, resume broken uploads, sync only what changed, and never silently overwrite someone else's work.

Problem statement
You're designing Dropbox-style file sync: files and folders across devices, chunked upload for large blobs, metadata revisions for every change, sharing permissions, conflict copies when two clients edit offline, and enough production discipline that a sync storm or bad client release does not corrupt data.
Introduction
Dropbox looks like a folder, but the system underneath is a careful agreement between devices, metadata, and blob storage. A user expects a file saved on a laptop to appear on a phone, survive a broken upload, and not vanish because a second laptop woke up with an old copy.
The core split is simple: metadata is the truth about the file system, while blob storage holds the bytes. Metadata knows path, file id, current revision, parent revision, sharing rules, tombstones, and the ordered list of chunks that make up a version. Blob storage keeps immutable chunks by content hash. A chunk is a piece of a file; immutable means once stored, it is not edited in place. A new file version points to a new manifest, which is the ordered chunk list for that version.
Weak answers say "put files in S3 and metadata in Postgres" and stop there. Strong answers explain how a client uploads missing chunks, commits a new revision only after the chunks exist, tells other devices through a change journal, and protects users from silent overwrites when two offline edits race.
If you remember one idea, make it this: Dropbox is not one big file table. It is a metadata journal plus content-addressed chunks, joined by version commits.
How to approach
Start by walking one file through the system. A laptop edits a file, uploads chunks, commits a revision, and another device catches up by reading a change journal. Once that story is clear, you can discuss sharing, offline clients, conflict resolution, and production storms.
Clarifying questions (say these in the room)
You: "Are we building file sync like Dropbox, or real-time co-editing like Google Docs?"
Interviewer: "File sync is enough; mention co-editing only as out of scope."
That answer keeps you on chunk manifests, revisions, and conflict copies instead of operational transforms or CRDTs.
You: "How large can files be, and do uploads need to resume after the network drops?"
Interviewer: "Assume multi-GB files and unreliable networks."
Now chunking is required, not optional, because one failed connection should not restart a huge upload from zero.
You: "Do we need version history and trash, or does delete remove bytes immediately?"
Interviewer: "Keep version history and trash for a retention window."
That answer affects garbage collection. A chunk cannot be deleted just because the latest file version no longer uses it; older versions or trash may still reference it.
You: "For conflicts, should the server auto-merge files, last-write-wins, or create conflict copies?"
Interviewer: "Create conflict copies for general files; auto-merge is not required."
This keeps the system honest. A binary file or slide deck cannot be safely merged by a generic storage service.
You: "I want to leave live document collaboration, full malware-scanning pipelines, and building a global CDN from scratch out of scope so we can focus on file sync correctness. Does that match the interview?"
Interviewer: "Yes, focus on sync, storage, sharing, conflicts, and scale."
Minute pacing (about 45-60 minutes)
| Minutes | Focus |
|---|---|
| 0-5 | Clarify sync vs co-editing, file size, version history, conflict policy, out of scope |
| 5-12 | Capacity and the metadata-vs-blob split |
| 12-25 | Upload path: chunks, missing hashes, commit with parent_rev |
| 25-35 | Sync path: journal cursor, offline replay, conflict copy |
| 35-45 | Sharing, permissions, delete/trash, garbage collection |
| 45-60 | Production scenes, failure modes, observability, and interview pushback |
In the room (a fuller opening you can actually say): "I will separate metadata from bytes. The client splits a file into chunks, hashes them, uploads only missing chunks, then commits a new file revision with parent_rev and an ordered chunk list. Other devices do not receive full folders by push; they receive a hint, call changes with their cursor, and download missing chunks. If two offline clients commit from the same parent revision, I will preserve both by returning a conflict or creating a conflict copy instead of silently overwriting."
Out of scope (and why you park these)
Live collaborative editing. Real-time cursor-by-cursor editing needs merge algorithms such as operational transforms or CRDTs. Dropbox-style file sync can mention that as a separate product, but the main system protects whole-file revisions.
A full malware and data-loss-prevention platform. Virus scanning and enterprise controls matter, but they can run asynchronously after upload or before share download. They should not hide the core sync design.
Designing object storage or CDN internals from scratch. Use S3-class durable object storage and a CDN for shared downloads. The interview is about how you address chunks, revisions, ACLs, and sync.
Permanent exact history for every byte. Version history and trash are product policies with retention windows. Keeping everything forever changes cost and privacy, so state your retention rule.
Capacity estimation
Use rough numbers to keep the shape honest. Metadata operations often dominate latency even though blobs dominate dollars.
| Dimension | Rough scale | What this means for the design |
|---|---|---|
| Users and devices | Hundreds of millions of users, multiple devices each | Sync cursors and device state are first-class data |
| Files and folders | Billions of metadata nodes | Metadata must be sharded by namespace or team, not stored as one global tree |
| Blob storage | Exabytes | Immutable chunks, lifecycle tiers, and GC visibility matter |
| Upload size | KB notes to multi-GB videos | Chunked resume and parallel upload are required |
| Shared folder changes | Thousands of clients may need the same update | Notify with hints; clients pull deltas instead of pushing full trees |
Three practical lessons follow. First, the metadata database is the user-visible control plane, so a slow metadata shard makes files look wrong even if object storage is healthy. Second, egress and retention dominate cost over time, especially when version history and shared links keep old chunks alive. Third, sync QPS can exceed upload QPS because every saved file may wake several devices.
High-level architecture
The high-level design has two planes and one notification loop. The metadata plane owns namespaces, paths, file ids, revisions, sharing, and the change journal. The blob plane owns immutable chunks in object storage. The notification loop tells devices that something changed, but the device pulls the actual delta using a cursor.
Who owns what:
- Sync client keeps a local folder mirror, a queue of offline changes, a chunk cache, and a
last_seqcursor per namespace. - Metadata service owns the authoritative tree, revisions,
parent_revchecks, tombstones, sharing edges, and the change journal. - Chunk gateway prepares upload sessions, verifies chunk hashes, skips chunks that already exist, and returns pre-signed URLs.
- Object store keeps immutable chunks, encrypted at rest, replicated across storage zones, and served through range-friendly download paths.
- Notification service sends "namespace changed" hints by WebSocket, long poll, or mobile push.
- Async workers create thumbnails, index search, scan for abuse, and garbage-collect chunks that no live reference needs.
[ Desktop / Mobile client ]
|
| 1) list changes, commit metadata, read ACLs
v
[ API / Metadata service ] -----> [ Metadata DB shards ]
| (namespace, file_id, rev, journal)
|
| 2) prepare missing chunks
v
[ Chunk gateway ] -------------> [ Object storage ]
| (chunks by hash, immutable)
|
+--> [ Notification service ] --> clients pull /changes
|
+--> outbox --> thumbnails / search / virus scan / GC
In the room: Narrate the synchronous path first: chunks upload, metadata commit, journal row. Then say thumbnails, search, and garbage collection trail behind and do not decide whether the user has a new file version.
Core design approaches
Whole-file upload vs chunked upload
Whole-file upload is easy to explain, but it punishes users on bad networks. If a 10 GB upload fails at 9.8 GB, the user starts again. Chunked upload splits the file into pieces, verifies each piece by hash, and resumes from the missing pieces. It also lets a small edit reuse unchanged chunks.
Fixed-size chunks vs content-defined chunks
Fixed-size chunks are simple: cut the file every few MB. The weakness is that inserting bytes near the front shifts every following chunk, so dedup becomes worse. Content-defined chunking cuts chunks based on the byte pattern, so insertions are less likely to shift every boundary. In an interview, fixed-size chunks are acceptable if time is short; content-defined chunks are a strong follow-up for delta efficiency.
Last-write-wins vs conflict copy
Last-write-wins means the latest commit replaces the previous one. It is simple and dangerous for offline sync because the "latest" device may be the one that woke up last, not the one with the correct work. Conflict copy preserves both versions when parent_rev does not match current rev. The user sees two files and can merge manually. For general file sync, conflict copy is safer and easier to defend.
Push full updates vs notify then pull
Pushing every changed path to every device sounds fast, but it creates storms for large folders and makes retry logic messy. A better default is "notify then pull": push only a lightweight hint that a namespace changed, and clients call the changes API with a cursor. The cursor makes retries safe and lets the server paginate large change sets.
Data model
The data model should make versioning and sync obvious. If you cannot point to rev, parent_rev, and a journal cursor, the design will struggle under offline edits.
| Entity | Key fields | Why it exists |
|---|---|---|
namespace | namespace_id, owner or team, root folder | Boundary for sharding, permissions, and change sequence |
node | node_id, parent folder, name, type, deleted flag | Represents file or folder in the tree |
file_version | file_id, rev, parent_rev, chunk manifest, size, author, created_at | Stable history of one saved file state |
chunk | chunk_hash, size, storage key, encryption info, refcount or references | Deduped immutable bytes |
journal_entry | seq, namespace, operation, node id, new rev, timestamp | What clients read during sync |
share_acl | resource, grantee, role, inherited or direct, version | Enforces read/write/list access |
device_cursor | device id, namespace, last acknowledged seq | Tracks catch-up and avoids full resync |
For files, store a manifest rather than a giant blob pointer. A manifest is the ordered list of chunk hashes for one revision. For folders, store parent-child relationships and decide whether renames are cheap or listings are cheap. A materialized path makes listing easy but large folder moves more expensive; parent pointers make moves easier but need careful pagination for huge folders.
Deletes should create tombstone revisions, which are journal entries saying the file was deleted. A tombstone lets other devices delete the local copy and lets version history or trash keep the old bytes until retention expires.
Detailed design
Think of Dropbox sync as two connected journeys: one client creates a new version, and every other client catches up without guessing. The first journey is chunk upload plus metadata commit. The second journey is change-journal sync plus chunk download. The design works because the metadata commit is small and authoritative, while large bytes move separately.
When a client saves a file, it does not immediately send one giant object and call the job done. It first scans the local file and splits it into chunks. For each chunk, the client computes a content hash, which is a fingerprint of the bytes. The client sends the list of hashes to the chunk gateway. The server answers which hashes already exist and which ones are missing. Existing chunks do not need to be uploaded again; missing chunks are uploaded through resumable sessions, often directly to object storage through pre-signed URLs.
After the chunks are present, the client calls CommitFile with the path, the ordered chunk list, a client-generated idempotency key, and the parent_rev it edited from. The parent_rev is the file revision the client believed was current when the edit started. The metadata service checks the current file revision. If the current revision still matches parent_rev, the service creates a new rev, writes the manifest, updates the tree, and appends a journal entry in one metadata transaction. If the current revision has moved on, the server must not silently replace it. It either returns a conflict response or creates a conflict copy with its own file name and revision.
That commit boundary is the important line. Bytes may upload before the file is visible. A notification may arrive after the file is visible. Search indexing and thumbnails can lag. The user-visible truth changes when the metadata transaction creates the new revision and journal entry.
Another device catches up from the journal. It keeps a last_seq cursor for each namespace. A WebSocket, long poll, or mobile push only wakes the device and says, "check your cursor." The client calls GET /changes?cursor=..., receives operations after that sequence number, and applies them in order. For an updated file, it fetches the new manifest, compares chunk hashes with its local cache, downloads missing chunks in parallel, and then swaps the local file when it has all required bytes.
Offline clients follow the same rule, just later. While offline, a device queues local edits with the parent revisions it saw. When it reconnects, it first pulls remote changes so it knows the current server state. Then it replays local commits. Any commit whose parent revision is stale becomes a conflict instead of a silent overwrite. This is why the conflict policy belongs in the core design, not in a tiny edge-case footnote.
Sharing is checked on both commit and read. A user with read-only access may download chunks through a short-lived URL, but cannot commit a new revision. A user whose access was revoked should fail at the metadata permission check before the system issues fresh download URLs. Cached permission views are useful, but revoke paths need invalidation or short TTLs because stale ACL reads are security bugs.
In the room (spoken recap): "A save is chunks first, commit second. The client hashes chunks, uploads missing bytes, then commits a manifest with parent_rev. The metadata service creates one new rev and journal entry if the parent still matches; otherwise it preserves both versions with a conflict copy. Other devices receive a hint, pull changes by cursor, fetch missing chunks, and apply the journal in order. Metadata is the truth; blobs are durable bytes."
Architectural dig
One offline edit conflict
This is the interview meat: two devices can both be honest and still disagree. The system must preserve work rather than deciding from clocks alone.
1) Metadata commit ownership
The metadata service owns the namespace sequence, the current file revision, ACL checks, and the append-only change journal. The blob store does not decide which file version is current. It only stores chunks. This keeps the "what changed?" question in one place.
2) Bad design: compare modified times
9:00 Laptop A edits report.pptx offline from rev=7
9:05 Laptop B edits report.pptx online and commits rev=8
9:30 Laptop A reconnects with an older wall clock
Server sorts by mtime or "last upload wins"
→ one version overwrites the other
→ user loses work with no clear explanation
Modified time is a weak authority because devices go offline, clocks drift, and upload order is not the same thing as user intent.
3) Great design: parent_rev and conflict copy
Laptop B commit: parent_rev=7, current=7 → create rev=8
Laptop A commit: parent_rev=7, current=8 → conflict
Result:
report.pptx (rev=8)
report (conflicted copy).pptx (new rev from Laptop A)
The server preserves both byte streams. It can return 409 Conflict and let the client create the conflict copy, or the server can create it directly. Either way, the user never loses a valid edit because the system preferred the wrong timestamp.

Figure: Wall clocks lose user work. Parent revisions preserve both versions and make the conflict visible.
4) Dedup and garbage collection inside the box
Chunk dedup saves money only if references are correct. A chunk may be referenced by the current version, an older version, a deleted file still in trash, a shared folder, or a legal hold. Garbage collection should run after metadata commits, read the authoritative references, and delete only chunks that are no longer reachable after the retention window. If you use refcounts, update them transactionally with version commits or use an epoch-based sweeper that tolerates retries.
5) Notification and sync storm control
The journal lets notifications stay small. A folder with 20,000 changed files should not push 20,000 payloads to every client. Send one "namespace changed" hint, let clients page the journal, and use backoff when they fall behind. If a buggy client keeps committing no-op changes, circuit-break that device id before it self-DDoSes metadata.
Interview land: "The conflict detector is not time; it is parent_rev against current rev. If they differ, preserve both versions."
Key challenges
| Challenge | What users see if it goes wrong | What to say in the interview |
|---|---|---|
| Large upload resume | A 10 GB upload restarts from zero | Chunk by hash, upload missing pieces, commit manifest after chunks exist |
| Offline conflict | Someone's edits disappear | Use parent_rev; create conflict copy instead of silent overwrite |
| Change fan-out | Devices spin and folders never settle | Notify with hints; clients pull paginated journal entries |
| Sharing revoke | Ex-user still downloads a file | Check ACL on metadata read; invalidate permission cache and signed URLs |
| Delete and version history | Storage grows after users delete files | Tombstones, retention windows, and GC metrics |
| Dedup privacy | Upload can reveal who has the same file | Limit dedup by tenant or encryption domain when privacy requires it |
The hard parts are not exotic algorithms. They are the everyday rules that prevent data loss: parent revisions, durable manifests, permission checks, and observable garbage collection.
Scaling the system
Shard metadata by namespace, usually a user root or team root. Most file operations happen inside one namespace, which keeps commits and journal sequence allocation local. Cross-namespace moves are rare and can be implemented as copy plus delete or a careful two-step operation rather than making the hot path distributed.
Object storage scales separately, but it does not make metadata free. Hot folders with millions of children need paginated listings and stable cursors. Shared folders with thousands of active devices need notification coalescing so one edit does not wake every device with a full payload.
Chunk upload can scale through stateless gateways because the chunks are addressed by hash and written to object storage. The gateway still verifies size and hash on complete, otherwise corrupted chunks poison every file version that references them.
Multi-region design should be honest. Blob replication can be asynchronous for durability and locality. Metadata for one namespace should usually have one primary region at a time so revision order and conflict detection stay understandable. If a team spans continents, use regional reads for downloads but route commits for that namespace to the authoritative metadata region, or accept a more complex multi-primary conflict story.
Failure handling
| Scenario | What the user sees | What to build |
|---|---|---|
| Chunk upload fails halfway | Upload says paused or retrying | Resumable sessions, missing-hash check, incomplete upload TTL |
| Commit times out after success | Client is unsure whether save worked | Idempotency key on commit; replay returns same revision |
| Client edits offline for hours | Conflict copy on reconnect | parent_rev check, clear local UX, preserve both versions |
| Metadata shard down | Folder state cannot be trusted | Fail closed for commits; allow cached reads only with stale label |
| Object store slow | Downloads lag but metadata may list file | Retry range downloads, mark file syncing, do not corrupt local cache |
| ACL cache stale | Revoked user can still access data | Short TTL, explicit invalidation, signed URL expiry |
The safe rule is: do not invent a current version unless metadata committed it. If chunks uploaded but commit failed, the chunks are orphan candidates. If metadata committed but notification failed, clients catch up from the journal later.
API design
Chunk upload
| Endpoint | Role |
|---|---|
POST /v1/chunks:prepare | Client sends chunk hashes; server returns which are missing and upload URLs |
PUT {pre_signed_url} | Client uploads one chunk or multipart part directly to storage |
POST /v1/chunks:complete | Server verifies hash and marks chunk available |
File metadata and sync
POST /v1/files:commit
Idempotency-Key: 9f3...
{
"namespace_id": "ns_123",
"path": "/team/report.pptx",
"parent_rev": "rev_7",
"chunks": ["sha256:a", "sha256:b"],
"client_modified_at": "2026-07-15T08:00:00Z"
}
| Endpoint | Role |
|---|---|
GET /v1/changes?namespace_id=...&cursor=... | Paginated journal changes after cursor |
GET /v1/files/{file_id}/versions | Version history subject to retention |
POST /v1/shares | Create shared folder or link with role policy |
DELETE /v1/files/{file_id} | Create tombstone revision, not immediate blob deletion |
Save path:
Client → chunks:prepare → upload missing chunks → files:commit(parent_rev)
|
v
journal seq appended
Sync path:
Notify hint → GET /changes?cursor=S → manifests → GET missing chunks
Important errors: 409 when parent_rev is stale, 403 when ACL denies access, 404 when a resource is not visible to the caller, 412 when quota is exceeded, and 429 when a device or namespace is syncing too aggressively.
Observability
Green upload success alone will lie. Measure the path from local save to all active devices catching up.
| Signal | Why it matters |
|---|---|
| Commit p99 by metadata shard | User-visible save latency and hot namespace pressure |
| Journal tail lag per namespace | Devices are falling behind even when APIs return 200 |
| Conflict rate by client version | Finds bad offline replay or clock bugs |
| Chunk dedup hit rate | Shows whether chunking is saving storage as expected |
| Orphan chunks and GC backlog | Prevents storage bills from growing after deletes |
| ACL deny and revoke propagation time | Confirms revoked shares actually stop access |
| Sync ops per device per minute | Detects client loops and battery-draining releases |
Alert on metadata commit latency, journal lag, and unusual conflict spikes. A silent rise in conflict copies after a client release is as important as a 5xx spike, because it may mean the app is replaying old parent revisions.
Security and abuse
Authenticate every metadata and chunk operation. Authorize at the metadata layer before issuing chunk download URLs; object storage URLs should be short-lived and scoped to the exact chunk or file range the caller may read. Sharing must enforce inherited ACLs, block lists, public-link expiry, password rules, and revoke.
Treat file names and paths as sensitive metadata. Even if chunks are encrypted, a path like /Legal/Acquisition.xlsx can reveal private information. Logs should avoid raw paths where possible, or keep them in access-controlled audit stores.
Abuse controls include rate limits on public links, malware scanning before broad sharing, anomaly detection on mass downloads, and audit records for enterprise admin access. Cross-user dedup has a privacy cost because the system can infer that two accounts uploaded the same content. Prefer tenant-scoped dedup when privacy or enterprise isolation matters.
Cost awareness
The biggest cost is usually not the metadata database. It is blob storage, egress, version retention, and duplicate downloads during sync storms. Chunk dedup reduces storage, but refcount or reference tracking must be correct or you keep bytes forever. Version history and trash are product features with storage invoices attached.
CDN and regional storage lower latency for shared downloads but can raise egress and replication costs. Search indexing, thumbnails, and malware scans should run asynchronously and be prioritized, because doing all of them for every intermediate version of a frequently saved file wastes compute. The cheapest sync notification is a hint plus cursor pull, not pushing full folder state to every device.
Production scenes
Production Dropbox failures often look like "sync is stuck" rather than "the server is down." Users care about whether the right file version appears on the right device. On-call must look beyond HTTP 200 and follow the journal, device cursor, and chunk manifest.
Scene 1: The sync storm after a client release
The moment: A new desktop client ships. Support tickets say laptops are hot, fans are loud, and "syncing 12 files" never finishes. Metadata APIs return 200. Global QPS looks high but survivable.
The trap: One client bug keeps writing no-op metadata changes, such as rewriting the same folder timestamp. Every no-op creates a journal entry. Other devices wake, pull changes, and sometimes write their own harmless-looking updates. The system is available, but the client fleet is self-DDoSing the metadata plane.
What to build: Track sync operations per device and per client version, not only per account. Add server-side no-op detection so identical commits do not create journal entries. Use circuit breakers that slow or pause a noisy client version. Give clients Retry-After and exponential backoff when journal lag grows.
bad client release
→ no-op commits
→ journal grows
→ devices wake and pull
→ more commits / battery drain
Scene 2: Conflict overwrite turns into data loss
The moment: A user says yesterday's presentation disappeared after their travel laptop came online. Logs show both devices uploaded successfully.
The trap: The system used last-write-wins based on upload arrival time. The travel laptop held an old copy, reconnected later, and overwrote the office laptop's newer work. Every API looked successful because the system had no idea the parent revision was stale.
What to build: Require parent_rev on commit. Return 409 or create conflict copy when current rev differs. Alert on conflict rate by client version and by namespace so a bug that drops parent revisions does not silently spread.
Scene 3: Deleted files keep billing storage
The moment: A company deletes a large folder and expects storage to fall. Billing keeps rising. Legal asks why deleted data still appears in internal storage reports.
The trap: Chunks are shared across versions, trash, shared folders, and retention holds. A delete only wrote a tombstone; it did not remove every reference. Garbage collection is correct eventually, but no one measured backlog or oldest retained byte.
What to build: Expose bytes by reference reason: current version, version history, trash, legal hold, shared link, orphan pending GC. Alert on GC backlog age. Give enterprise admins clear retention reports so cost and compliance conversations do not depend on guesswork.
Scene 4: Share revoke is slow in one region
The moment: An employee leaves a company. Admin revokes a shared folder. The employee can still download files for a few minutes from a cached link in another region.
The trap: The data plane accepted a signed URL that outlived the permission change. The metadata revoke was correct, but the chunk download path did not re-check access or use short enough URL expiry for sensitive shares.
What to build: Keep signed URLs short-lived, tie them to permission version when needed, and invalidate permission caches on revoke. For sensitive folders, route downloads through a token check that can fail closed rather than relying on long-lived object-store URLs.
In the interview: Pick one scene and tell it as a user story: sync loop, conflict overwrite, GC backlog, or slow revoke. Name the metric that exposes it and the containment lever you would ship.
Bottlenecks and tradeoffs
Metadata consistency vs blob scale
The blob plane can scale widely because chunks are immutable. The metadata plane cannot be hand-wavy because it decides current revision, permissions, and journal order. If metadata is eventually inconsistent in the wrong place, users see wrong folders or stale revokes.
Dedup savings vs privacy
Global dedup can save a lot of storage when many users upload the same file. It can also reveal that a user has a known file if the system exposes timing or "already uploaded" behavior. Tenant-scoped dedup and encryption-domain boundaries cost more storage but reduce privacy risk.
Conflict safety vs user convenience
Conflict copies annoy users because they must merge manually. Silent overwrite is worse because it loses work. For general files, preserve both and let product-specific editors offer smarter merge later.
Fast sync vs battery and backend cost
Polling every few seconds feels fresh but drains batteries and metadata QPS. Push hints plus cursor pull are cheaper, but clients need backoff and jitter so a large folder update does not wake every device at once.
Interview tips
You have the full design in mind now. These are the back-and-forths interviewers use to test whether you understand sync, not only storage.
"Store by path in S3" misses the version story
You might say: "The file path maps to an S3 object, and uploading replaces that object."
Interview Push: "A user edits offline on two laptops. Which object wins, and how do you avoid losing work?"
Land here: Path is not enough. Store metadata with file_id, rev, parent_rev, and a chunk manifest. A commit succeeds only if the parent revision still matches current. If it does not, preserve both versions with a conflict copy. The object store holds immutable chunks; metadata decides which manifest is current.
Modified time is not a sync protocol
You might say: "Clients compare modified times and upload the newest file."
Interview Push: "What if one laptop has the wrong clock or reconnects later?"
Land here: Device clocks are hints for UI, not authority. The server journal sequence tells clients what changed, and parent_rev tells whether a commit was based on the current file. Sorting by local modified time can overwrite newer work with an older offline copy. Use server revisions for correctness.
Push should not carry the whole folder tree
You might say: "When a file changes, we push the full update to every device."
Interview Push: "A shared folder with 100,000 files changes permissions. Do all phones receive the full tree?"
Land here: Push is a wake-up hint. Clients pull a paginated change journal with a cursor and apply entries in order. This makes retries safe, lets slow devices catch up gradually, and prevents a large folder update from becoming a notification storm.
Dedup is not free once privacy matters
You might say: "We hash every chunk globally, so identical chunks are stored once."
Interview Push: "Can that reveal that two customers uploaded the same sensitive file?"
Land here: Yes, global content addressing can leak information through behavior or internal access patterns. Many systems limit dedup to a tenant, encryption key space, or server-side encryption model. Name the cost tradeoff: more storage for stronger isolation.
Delete is not immediate physical deletion
You might say: "When a user deletes a file, we delete the chunks."
Interview Push: "What about version history, trash, shared folders, and legal holds?"
Land here: Delete writes a tombstone and updates the journal so devices remove the file locally. Physical chunk deletion waits until no live version, trash entry, share, or hold references the chunk. Garbage collection is asynchronous and observable through backlog metrics.
Red flags to avoid without correcting yourself: "Last write wins for all conflicts," "sync by modified time," "push full folders," "delete chunks immediately," "share links never need re-check after revoke."
What should stick
After reading this guide, you should be able to explain:
- Two planes — metadata owns namespace, revisions, ACLs, and journal; blob storage owns immutable chunks.
- Chunks before commit — upload missing hashes first, then commit one version manifest.
- Journal beats push payloads — notifications wake devices; cursors pull the authoritative change list.
parent_revprotects work — stale offline commits become conflicts instead of overwrites.- Deletes need retention and GC — tombstones sync first; physical chunk deletion waits for references to disappear.
Tell it in the room: "Client chunks and hashes the file, uploads missing chunks, then commits a manifest with parent_rev. Metadata creates a new rev and journal entry. Other devices get a hint, pull changes by cursor, and download missing chunks. If the parent revision is stale, preserve both versions with a conflict copy."
Key Takeaways
- Separate truth from bytes — metadata tells you current versions; blob storage stores chunk data.
- Use chunked upload — resume large files and avoid uploading bytes the system already has.
- Commit atomically — new revision and journal entry must appear together.
- Design for offline — clients replay local edits with
parent_rev; conflicts preserve work. - Notify then pull — push hints keep fan-out cheap, while cursors make catch-up reliable.
- Security is on the read path — ACLs and share revokes must be checked before issuing download access.
- Operate sync, not just HTTP — watch journal lag, conflict spikes, GC backlog, and noisy clients.
Related Topics
- Google Drive System Design - Similar file storage questions with collaboration follow-ups
- Notification Service System Design - Waking clients without pushing full state
- Distributed Cache System Design - Metadata caching, invalidation, and hot keys
- Message Queues - Async thumbnails, search indexing, scanning, and GC
- URL Shortener System Design - Token design patterns that apply to secure share links
Quick revision notes
Use this as a last look before a mock interview, not as a replacement for the full walkthrough.
Clarify first. Ask whether the product is file sync or live co-editing, how large files can be, whether uploads must resume, how long version history lasts, and what conflict policy the product wants. These answers decide whether chunks, parent_rev, and tombstones are central or optional.
Draw two planes. Metadata handles paths, revisions, ACLs, and the change journal. Blob storage handles immutable chunks. Do not let object storage decide which revision is current.
Walk one save. Hash chunks, upload missing pieces, commit a manifest with an idempotency key and parent_rev, append a journal entry, then notify devices. Other devices pull changes by cursor and fetch missing chunks.
Protect offline work. If the current server revision differs from the client's parent revision, create or return a conflict copy. Last-write-wins may look simple, but it loses data when devices reconnect out of order.
Operate the sync loop. Watch journal lag, conflict rate, noisy client versions, orphan chunk bytes, GC backlog, and permission-revoke latency. A system can return 200 and still leave users with stuck sync or stale access.
Practice this path: Dropbox System Design.
What interviewers expect
A strong answer separates the metadata plane from the blob plane. The metadata plane owns namespace, paths, revisions, ACLs, and a change journal. The blob plane stores immutable chunks by hash in object storage. Uploads prepare and send missing chunks first, then commit a new file version with parent_rev. Other devices receive a lightweight notification and pull changes by cursor. Conflicts return 409 or create a conflict copy instead of overwriting silently.
Interview workflow (template)
- Clarify requirements. Confirm functional scope, users, consistency needs, and which non-functional goals matter most (latency, availability, cost).
- Rough capacity. Estimate QPS, storage, and bandwidth so your data model and partitioning story are grounded.
- APIs and core flows. Define a minimal API and walk 1–2 critical read/write paths end to end.
- Data model and storage. Choose stores for each access pattern; call out hot keys, indexes, and retention.
- Scale and failure. Add caching, sharding, replication, queues, or fan-out as needed; say what breaks in failure modes.
- Tradeoffs. Name alternatives you rejected and why (e.g. strong vs eventual consistency, sync vs async).
Frequently asked follow-ups
- How do you upload and resume a large file?
- How does another device know what changed?
- How do conflicts work when two devices edit offline?
- How do chunk deduplication and garbage collection stay correct?
- How are share links and folder permissions enforced?
Deep-dive questions and strong answer outlines
Walk through uploading a 10 GB file on flaky Wi-Fi.
The client splits the file into chunks, computes a hash for each chunk, asks the server which chunks already exist, uploads only missing chunks through resumable sessions, and then commits a manifest that lists the chunk hashes in order. The commit is the moment a new file revision appears in metadata; partial chunks are cleaned up later if no commit references them.
How does sync work on a second device?
Each client keeps a namespace cursor. A push, long poll, or periodic check only says something changed. The client calls a changes endpoint with its cursor, receives journal entries such as file updated or deleted, then fetches chunk lists and downloads only missing chunks. The metadata journal is the source of truth for what changed.
What happens when two devices edit the same file offline?
Each commit includes the parent revision the client edited from. If the current server revision no longer matches that parent, the server does not silently replace the newer file. It returns a conflict or saves a conflict copy, such as report (conflicted copy from Swati's Mac). The product may later offer merge tools, but the system design must first preserve both byte streams.
How does deduplication save storage without losing data?
Chunks are named by content hash and stored immutably. File versions reference chunk hashes, and metadata tracks references so garbage collection deletes a chunk only after no live version, trash entry, share, or retention hold points to it. Cross-user dedup can leak information, so many products limit dedup to a tenant or encrypted key space.
How do share links stay secure?
A share link maps a token to a resource and permission policy. The read path checks token validity, expiry, revocation, ACL inheritance, and sometimes password or domain restrictions before issuing download URLs. Cached permissions need short TTLs or invalidation so revoke takes effect quickly.
AI feedback on your design
After a practice session, InterviewCrafted summarizes strengths, gaps, and interviewer-style expectations—similar to a written debrief. See a static example report, then practice this problem to get feedback on your own answer.
FAQs
Q: Is Dropbox the same as Google Docs live collaboration?
A: No. Dropbox-style sync usually works at the file or block level and resolves concurrent edits with revisions and conflict copies. Live document editing uses operational transforms, CRDTs, or another real-time merge model. You can mention that as a separate product surface, but do not turn the Dropbox interview into a Google Docs design unless the interviewer asks.
Q: Why not store each file as one object?
A: One object per file is simple, but it makes resume and small edits expensive. With chunks, a broken 10 GB upload can resume from the missing pieces, and a small edit may upload only changed chunks. The metadata commit still presents one file to the user; chunks are an internal storage shape.
Q: What does version history change?
A: Version history means old manifests remain reachable for some retention window. Deletes become tombstone revisions first, not immediate blob deletion. Garbage collection must respect trash, legal holds, shared folders, and version-retention settings before removing chunks.
Q: Can the server dedupe encrypted files?
A: It depends on the encryption model. If clients encrypt with unique keys before upload, the same plaintext file produces different ciphertext chunks, so cross-user dedup does not work. Server-side encryption after hashing allows dedup, but gives the server more knowledge. Say the privacy and cost tradeoff out loud.
Q: Does push notification contain the full change?
A: Usually no. Push only wakes the client or says a namespace changed. The client then pulls the authoritative change list using its cursor. Pushing full folder trees to every device creates sync storms and makes retries harder.
Practice interactively
Open the practice session to use the canvas and stages, then review AI feedback.