EasyProblem 2 of 36 in the system design series
Design Dropbox: Cloud File Storage and Multi-Device Sync
Prefer watching? This article is the written companion to the video above, from the Shubham Aher YouTube channel.
A cloud file service lets someone save a document into a folder on one machine and find the same document waiting on their phone moments later. Behind that small convenience sit three demanding jobs: accepting files that may be tens of gigabytes, keeping several devices consistent with each other, and storing petabytes of user data without losing a single byte.
Interviewers pick this problem because it forces candidates to separate two very different kinds of data. Names, folders and version lists are small and relational. File contents are enormous, opaque and expensive to move. Candidates who push everything through one service and one database run into trouble quickly; those who split the control plane from the data plane find the rest of the design falls into place.
What makes it hard is the environment. Home Wi-Fi drops, laptops go offline for days, two people edit the same file on a plane, and the same popular installer gets uploaded by thousands of users. The design below for a Dropbox-like service treats each of those situations as a first-class requirement rather than an edge case.
This is an interview practice design for a Dropbox-like service, built from first principles. It is not a description of how Dropbox is actually built.
Functional Requirements
Four features define the product. Users can upload files from any device, and those files can be very large, so assume a ceiling of 50 GB per file. Users can download anything they have stored from any device they sign in on. Changes propagate on their own: edit a spreadsheet on the desktop and the phone receives the new version with no manual refresh. Finally, users can share a file or folder, either by link or by inviting specific people.
Real-time collaborative editing, where two cursors type into one document together, is explicitly left out. That is an operational-transform or CRDT problem with very different constraints, and mixing it in would blur the storage and sync story that this question is really testing.
Upload files
Upload from any device, including very large files.
up to 50 GB
Download files
Get any stored file back, on any device.
Automatic sync
Change a file on one device, see it on all the others.
no manual refresh
Share
Give other people access to a file or a folder.
link or invite
- Out of scope today: live co-editing like Google Docs
Non-Functional Requirements
Durability ranks above everything else. A user will forgive a slow upload, but not a missing tax return, so once the system acknowledges a file it must survive disk failures, machine failures and even the loss of a data centre. Object stores with multi-zone replication exist precisely for this guarantee.
Availability comes next, and we choose it over strict consistency. If a phone briefly shows the version from a few seconds ago, nobody is harmed; if the service refuses to open files during a network partition, everybody is. Two further requirements shape the client: big uploads must be resumable, picking up from where they stopped instead of from byte zero, and sync must be quick enough that changes appear elsewhere within seconds rather than minutes.
Durability
An uploaded file must never be lost.
zero data loss
High availability
Files are reachable any time; a few seconds stale is fine.
availability first
Resumable uploads
Big uploads survive network drops and continue.
resume, not restart
Fast sync
A change reaches other devices within seconds.
seconds, not minutes
Scale Estimation
Assume 500 million registered users, of whom 100 million are active on a given day. If each active user creates or modifies two files daily, that is 200 million uploads per day, or roughly 2,300 per second averaged over the day. With a typical file of about 1 MB, the system ingests around 200 TB of fresh data every day.
The total footprint is the eye-opening figure. If the average account holds about 10 GB, then 500 million accounts amount to roughly 5 EB. No relational database could hold that, and no fleet of application servers should be streaming it. Meanwhile, the metadata describing those files, such as names, paths, sizes, owners and chunk lists, is tiny by comparison. That contrast is the most important output of the estimate: keep file metadata in a database and file contents in object storage.
100million
Daily active users
500 million total users
200million
Uploads per day
≈ 2,300 per second avg
200TB
New data per day
200M files × 1 MB avg
5EB
Total storage
500M users × 10 GB
Metadata is tiny, bytes are huge → store them separately
Core Entities & API
Four entities cover the domain: the file itself, its metadata record, the chunks its bytes are split into, and the user who owns it. Treating chunks as a first-class entity is what later enables resumable uploads and deduplication.
Uploading starts with POST /files, sending the name, the size and an array of chunk hashes. The server answers with a file_id and a set of upload URLs, one for each chunk it still needs. Downloading uses GET /files/{file_id}, which returns metadata plus a download URL instead of streaming bytes itself. Syncing relies on GET /changes?cursor={c}, which lists every change after the given position along with a fresh cursor the device stores for its next call. Note that none of these endpoints carry file contents; they only coordinate.
Core entities
FileFile metadataChunkUser
API
- POST /files { name, size, chunk_hashes[] }→ { file_id, upload_urls[] }
- GET /files/{file_id}→ { metadata, download_url }
- GET /changes?cursor={c}→ { changes[], next_cursor }
High-Level Design
Desktop and mobile clients talk to an API gateway that authenticates requests and enforces rate limits. Behind it, the file service owns metadata: file names, sizes, owners, versions and ordered chunk lists, kept in a relational store such as Postgres where folder hierarchies and sharing permissions are easy to express with joins and constraints.
File bytes take a different route entirely. The client uploads chunks directly to object storage such as S3 using short-lived presigned URLs handed out by the file service, and downloads are served through a CDN. Our application servers therefore stay small and cheap, since they never touch the hundreds of terabytes moving each day.
Every metadata change is also appended to a per-user change log, for example a Kafka topic partitioned by user so events for one account stay in order. A sync service consumes those events and nudges the user's other connected devices with a small notification. The guiding split is easy to remember: coordination flows through our services, heavy content flows straight to storage.
Metadata through our servers · bytes straight to storage
Deep Dive 1 — Chunked Resumable Uploads
A single 2 GB upload over a home connection will almost certainly be interrupted at some point, so the client breaks the file into fixed pieces of about 4 MB each, turning that video into roughly 500 chunks. It then computes a SHA-256 digest of each piece. The digest doubles as the chunk identifier, which means any two identical pieces of data share the same name everywhere in the system.
Next, the client registers the file by sending its list of digests. The file service creates a record with an uploading status and returns a presigned URL only for chunks that storage does not already hold. A presigned URL is a time-limited permission to write exactly one object, so the client can upload without our servers proxying the data. Several chunks go up in parallel to use the available bandwidth.
If the connection breaks at 90 percent, the client simply asks the service which chunks have arrived and sends the remainder; nothing already stored is repeated. When every chunk is confirmed, the file service flips the record to ready and emits a change event so other devices learn about the new file.
- 1Split into chunksClient cuts the file into 4 MB pieces
- 2Fingerprint each chunkSHA-256 of the bytes becomes the chunk ID
- 3Get presigned URLsOne short-lived upload URL per missing chunk
- 4Upload in parallelSeveral chunks at once, straight to S3
- 5Resume after a dropAsk what arrived; send only the rest
- 6Commit the fileAll chunks present → status = ready
- Presigned URL = time-limited permission to write one object
Deep Dive 2 — Sync Across Devices
The naive way to learn about remote changes is for every device to poll on a timer. A long interval makes sync feel sluggish, while a short one multiplies requests across 100 million users into a constant storm of empty responses. Neither setting is acceptable.
The better approach keeps one lightweight persistent connection per device, using a WebSocket or a long poll. When the change log records an update, the sync service sends only a tiny signal. The device then calls the changes endpoint with its stored cursor, which acts like a bookmark into that user's log. Whether the laptop was offline for ten seconds or ten days, it receives exactly the events it missed and nothing more. The cost is holding many open connections, which a dedicated, horizontally scaled sync tier can absorb.
Applying a change is efficient too. The device compares the new chunk list with what it already has and fetches only the differing chunks, so editing one page of a large document moves a few megabytes, not the whole file. If two devices edited the same file while offline, the system does not attempt a clever merge: the first version to commit wins and the other is preserved as a conflicted copy, so no edits disappear.
Client polling
- + Very simple
- − Slow, or wasteful
- − Huge load at 100M users
WebSocket / long pollChosen
- + Changes in seconds
- + Only a tiny ping is sent
- − Many open connections
- Cursor = bookmark in the user's change log → fetch only what you missed
- Delta sync: download only the chunks that changed
- Offline edit conflict → keep both as a 'conflicted copy'
Deep Dive 3 — Deduplication
Because each chunk is named by the hash of its own contents, deduplication comes almost for free. Identical chunks produce identical identifiers, so storage keeps a single copy. The upload flow already checks digests before sending data, which means copying a large file into another folder, or re-uploading something already stored, completes nearly instantly because no bytes need to travel.
Shared chunks complicate deletion, so each chunk carries a reference count of the file versions that point at it, and garbage collection removes a chunk only when that count reaches zero. Chunks are also compressed, and data that nobody has opened in months can move to colder, cheaper storage tiers. One caution: deduplicating across different accounts saves the most space but can leak information, since an instant upload reveals that someone else already holds that exact file. Scoping deduplication to a single account avoids that side channel.
Content-addressed chunks
A chunk's ID is the hash of its bytes, so identical chunks are stored once.
Check before uploading
Client sends hashes first; chunks we already have are skipped.
Reference counting
Delete a chunk only when no file version points to it anymore.
Compress and tier
Compress chunks; move files nobody opens to colder, cheaper storage.
Mind privacy
Cross-user dedup can reveal that a file exists, so scope it per user.
Trade-offs
Chunk size is a balancing act. Smaller chunks improve deduplication and make resumes cheaper, while larger chunks reduce request counts and the size of each file's chunk list. Favouring availability keeps files reachable at all times, at the cost of a device occasionally showing a slightly older version for a few seconds.
Sending uploads directly to S3 keeps application servers lean, but the file service must now track the status of each chunk and make sure presigned URLs expire. Saving conflicted copies rather than merging guarantees no work is lost, though the user has to reconcile the two versions manually. Taken together, the design rests on three habits: metadata lives in a database, bytes live in object storage, and only the parts that changed ever move across the network.
Chunk size
Small: better dedup and resume. Large: fewer requests, less metadata.
Availability over consistency
Always reachable — but a device may see an old version for a few seconds.
Direct uploads to S3
Light, cheap servers — but we must track chunk status and expire URLs.
Conflicted copies, not merging
No work is ever lost — but the user resolves the conflict by hand.
Metadata in a DB. Bytes in object storage. Move only what changed.
Key takeaways
- Keep small metadata in a relational database and the exabytes of file bytes in object storage.
- Split files into hashed chunks so uploads can resume and run in parallel.
- Presigned URLs let clients write straight to S3, keeping application servers light.
- A per-user change log plus a cursor lets any device catch up on exactly what it missed.
- Content-hashed chunks give deduplication; reference counts make deletion safe.
- Prefer conflicted copies over merging so offline edits are never silently lost.
Interview questions
How does Dropbox-style chunked upload work for large files?
The client splits the file into fixed-size pieces, often around 4 MB, and hashes each one. It sends the list of hashes to the server, which returns presigned upload URLs for chunks it does not already have. The client uploads those chunks in parallel directly to object storage, and the server marks the file ready once every chunk is confirmed.
How do you resume a failed file upload in a cloud storage system?
Track the status of every chunk on the server. After a dropped connection, the client asks which chunks were received and uploads only the missing ones, using fresh presigned URLs if the old ones expired. Because each chunk is small and identified by its hash, a failure costs at most one chunk of rework instead of restarting the entire file.
Why upload files directly to S3 instead of through the API server?
At hundreds of terabytes per day, routing bytes through application servers would require a huge fleet whose only job is copying data. Presigned URLs give the client a short-lived permission to write one object straight to storage, so the servers handle only lightweight metadata calls. This cuts cost, reduces latency and removes a large failure surface.
How does a file sync service notify other devices of changes?
Each device keeps a WebSocket or long-poll connection to a sync service. When the per-user change log records an update, the service sends a small notification. The device then requests all changes after its saved cursor, diffs the old and new chunk lists, and pulls just the pieces that differ. The cursor lets a device catch up correctly even after a long offline period.
What happens when two devices edit the same file offline?
Automatic merging of arbitrary binary files is unreliable, so a common policy is first commit wins. Whichever device syncs first becomes the current version, and the other device's edit is saved alongside it as a conflicted copy. No changes are lost, and the user decides how to combine the two versions instead of the system guessing.
Keep practising
- Learn the building blocks in the free system design course.
- Rehearse answers with system design interview questions.
- See which open jobs fit your skills: upload your resume.