Vibe Engines
YouTube

Vibe Engines is free, interactive AI engineering & system design — runs in your browser. Pick a learning path

System Design

Design Dropbox

Step 1 / 9

Learn system design by building a file sync service like Dropbox step by step.

The numbers to beat4 MBblock sizehash= addressstore oncededup

The whole design, in writing

Every step of the build above, written out: the problem each piece solves, the option that was taken and the ones that were not, the numbers, and how it fails in production.

The big idea

How does Dropbox sync your files?

You drop a file in a folder on your laptop. Moments later it’s on your phone, your tablet, and a colleague’s machine — and if you tweak one paragraph, only that change travels, not the whole file again.

LaptopPhone
New in this step: Laptop, Phone. · swipe to pan the diagram

Sync is really two problems wearing one coat: moving bytes efficiently, and keeping a consistent view of the namespace (the file tree, names, versions) across devices that come and go. We’ll build those two halves and the machinery that keeps them agreeing.

What the new pieces do

Laptopdevice
A device where files change. A local client watches a folder and uploads only what's new, then keeps it in sync.
Phonedevice
Another device on the same account. Must learn about the laptop's change quickly and pull it down.

Step 1 · The skeleton

Files = metadata + bytes

A "file" is two things: where it lives and what it’s called (metadata), and the actual content. Conflating them makes both sync and storage harder.

App ServerMetadata ServiceMetadata DB
New in this step: App Server, Metadata Service, Metadata DB. · swipe to pan the diagram

A "file" is its name/path (metadata) plus the actual bytes (content). How do you model it for sync?

  1. Conflating the tiny mutable metadata with the giant immutable bytes makes both sync and storage harder: every rename rewrites bytes, and you can’t sync "what changed" cheaply. Split them.

  2. A Metadata DB owns folders, names and versions; content goes elsewhere. Devices sync the small metadata first to learn exactly what changed, then fetch only the bytes they’re missing. The file tree, not the files, is kept consistent.

  3. Device-local trees can’t agree across machines that come and go — you need an authoritative server-side namespace to sync against. The metadata is the shared source of truth.

Split them. A Metadata Service (backed by a Metadata DB) owns the folder tree, names, and versions. Content goes elsewhere. Uploading a file becomes: store the bytes, then record a row saying "this path, this version, is made of these bytes."

Why this piece earns its place

Splitting the file in two means one upload now writes to two stores with no transaction across them, so the ordering is a design decision, not a detail. Bytes go first and the metadata row commits second: until that row lands the file does not exist, and a device that dies mid-upload has merely left blocks nobody points at. Do it the other way and the tree advertises a file whose bytes were never stored — a download that fails on something the user can plainly see in their folder. The cost of that ordering is garbage, because every abandoned upload leaves blocks behind. You cannot reclaim them by deleting whatever the last version dropped, either: the same bytes may be referenced by an older version you still owe the user, or by a completely different account. Collection has to run from the metadata side — reference counts, or a sweep over live block lists — and it has to treat a block that is being written right now as live, or it will cheerfully delete an upload that is halfway through.

What the new pieces do

App Serveredge
The front door for every device. Routes metadata operations and coordinates uploads and downloads.
Metadata Serviceservice
Owns the logical view: folders, file names, versions, and which blocks make up each file. The brain of sync.
Metadata DBstore
Durable record of the file tree, versions, and block lists. The source of truth for what the namespace looks like.

Step 2 · Chunk & dedup

Don’t move the whole file

Re-uploading a 2 GB file because you changed one slide is absurd — and storing the same attachment a thousand users share a thousand times is wasteful.

Block Servicechunks + dedupBlock StoreS3 · chunks
New in this step: Block Service, Block Store.

Re-uploading a 2 GB file for a one-slide change is absurd, and storing a shared attachment 1000× is wasteful. Fix?

  1. Compression shrinks one upload but you still re-send the entire file on any change, and identical files from different users are stored separately. You need to move and store only the parts that are actually new.

  2. Per-user whole-file copies miss global dedup (the same block shared across users) and still move whole files. Go finer-grained: content-addressed blocks.

  3. A file becomes an ordered list of block hashes; a block named by its content hash is stored once globally, so if its hash already exists you skip the upload. Identical data — across files and users — is stored a single time.

The Block Service splits every file into fixed-size blocks (say 4 MB), hashes each, and stores blocks in a content-addressed Block Store. A file’s metadata becomes an ordered list of block hashes. If a block’s hash already exists, you skip the upload entirely — deduplication.

Why this piece earns its place

Block size is the knob this step introduces, and 4 MB is a compromise rather than a constant. Push it up and each block is cheaper to track but blunter to reuse: change one byte and you re-send the whole block, and two files have to agree over a much longer run of bytes before they dedup at all, so the win evaporates exactly where near-identical files should have paid off. Push it down and reuse gets sharper, but a 2 GB file becomes an enormous list of hashes that the metadata row has to carry, sync and version — the metadata you split out precisely to keep small — and every block is one more lookup into a global index keyed by hash, which means no locality at all: consecutive blocks of one file land on unrelated machines. Set it from measured file and edit sizes, not taste. And the hash must be cryptographic, because here the hash is the name: if two different blocks can be made to collide, one account can overwrite what another account’s tree points at.

  • 4 MBblock size
  • hash= address
  • store oncededup

What the new pieces do

Block Serviceservice
Splits files into fixed-size blocks, hashes each one, and stores only blocks it hasn't seen before.
Block Storestore
Object storage holding the actual file bytes as deduplicated, content-addressed blocks.

Back of the envelope

file = ordered list of block hashes
4 MB content-addressed blocks
hash already exists ⇒ skip upload
identical data stored once, globally
shared attachment ×1000
one stored copy

Step 3 · Delta sync

Send only what changed

Even within one file, you usually touch a tiny fraction. Sending every block on every save wastes bandwidth and battery.

LaptopPhoneApp ServerMetadata ServiceBlock ServiceMetadata DBBlock Store
The system as it stands at this step. · swipe to pan the diagram

Even within one file you usually touch a tiny fraction. How do you avoid sending every block on every save?

  1. Because a file is a list of content-addressed blocks, "what changed" is a set difference of hashes — delta sync. The network only ever carries genuinely new bytes; metadata records a new version pointing mostly at existing blocks.

  2. Labeling it small doesn’t reduce what’s sent — you still move every block. The win comes from computing the block-level diff and transferring only the changed blocks.

  3. A text/line diff doesn’t generalize to binary files (images, videos, zips) and is fragile across formats. Block-hash set difference works for any file type uniformly.

On a change, the client re-chunks the file, compares the new block hashes against the old list, and uploads only the new blocks. The metadata service records a new version pointing mostly at existing blocks plus a few new ones. Downloads work the same in reverse — fetch only the blocks you don’t already have.

Why this piece earns its place

This step rests on an assumption the previous one made quietly: that block boundaries stay put. With fixed-size blocks they only stay put for overwrites. Insert a paragraph at the top of a document and every boundary after it slides along, so every hash downstream of the edit is new and the set difference degenerates into the entire file — delta sync silently becomes a re-upload, and the user watches a one-word change cost what the original save cost. The fix is to stop cutting at fixed offsets and let the bytes choose the cuts: run a rolling hash across the file and end a block wherever that hash hits an agreed pattern, so a boundary is a property of the data around it and an insertion disturbs only the block it landed in. That is what production sync engines do, and it is worth its price — the client must read and hash the file to compute the diff at all, so delta sync is really local I/O spent on every save to avoid spending bandwidth.

  • ~few %of file moves
  • set diffof hashes
  • versionedmetadata

Back of the envelope

re-chunk ⇒ set-diff the hashes
only new blocks travel
~few % of a file moves per save
the rest already exist
new version = mostly old blocks
+ a few new ones

Step 4 · Multi-device

Tell the other devices, now

The laptop uploaded a change — but the phone has no idea. Polling "anything new?" every few seconds is laggy and hammers your servers with millions of empty checks.

Notification Svcpush changesSync Eventsstream
New in this step: Notification Svc, Sync Events.

The laptop uploaded a change but the phone has no idea. How do other devices learn quickly without hammering your servers?

  1. Millions of devices polling is laggy (you wait a poll interval) and floods your servers with empty checks. You want the server to tell devices the instant something changes.

  2. Wrong channel and far too slow/noisy for file sync — and it doesn’t drive an automatic pull. Devices need a lightweight real-time nudge, not a notification to a human.

  3. On commit, the metadata service emits a sync event; the notifier pushes "something changed" to affected online devices, which pull the new metadata and missing blocks. Push (light) then pull (heavy) — and via the event stream the writer never needs to know who’s online.

Add a Notification Service that holds a long-lived connection (or long-poll) to every online device. When the metadata service commits a change, it emits a sync event; the notifier pushes a nudge to the affected devices, which then pull the new metadata and missing blocks.

Why this piece earns its place

A push is a hint, not a delivery guarantee, and treating it as one is what keeps it cheap. Sockets drop, a nudge is lost, a laptop sleeps mid-event — so every client keeps a slow periodic poll underneath the connection as a floor, and the push only decides whether sync is instant or one poll interval late. That also makes the notifier restartable, which matters because its interesting failure is not going down, it is coming back: every device on every account reconnects in the same second and each immediately pulls metadata, so the metadata service takes its worst read spike at the moment it is least ready for one. Randomised reconnect backoff is half the defense; keeping the nudge payload-free is the other half, because a device that receives ten of them in a burst collapses them into a single pull. Fan-out is the number to watch here — one write into a shared folder wakes every member’s devices, so this tier scales with how many people subscribe, not with how much actually changed.

What the new pieces do

Notification Svcservice
Holds long-lived connections to every device and tells them the instant something they care about changes.
Sync Eventsbus
A stream of change events. Lets the metadata service announce updates without knowing which devices are online.

Step 5 · Serve downloads

Popular files, everywhere

When a shared file goes viral inside a company, thousands of devices pull the same blocks from the origin block store at once — slow for far-away users and heavy on the origin.

PhoneApp ServerBlock ServiceNotification SvcBlock StoreCDN / Cache
New in this step: CDN / Cache. · swipe to pan the diagram

A shared file goes viral — thousands of devices pull the same blocks from origin at once. How do you serve downloads?

  1. A block named by its hash can never change, so it caches anywhere with zero invalidation. Downloads fetch hot blocks from the nearest edge; origin only serves misses and the long tail — downloads scale almost for free.

  2. Replicas add origin throughput but distant users still pay the long round-trip, and you re-serve the same hot block repeatedly. Cache it at the edge near users instead.

  3. Throttling punishes legitimate use to mask a scaling gap. Immutable blocks are the perfect cache target — push them to a CDN rather than slowing everyone down.

Put a CDN / cache in front of the block store. Blocks are immutable and content-addressed — a perfect cache target. Downloads fetch hot blocks from the nearest edge; the origin only serves cache misses and the long tail.

Why this piece earns its place

Content addressing is what makes the edge nearly free, and it is also what makes the edge dangerous: a block’s name is its content, so whoever holds the hash can ask for the bytes. Permissions live in the metadata tree, and the CDN cannot read that tree — it sees an opaque key and a request. So authorisation has to happen before the fetch rather than during it: the app server checks the share on the metadata side and hands back a short-lived signed URL, while the edge caches on the block key alone and ignores the signature, so two people who are both entitled to the same block still share one cached copy. Keep the expiry short, because an issued URL cannot be recalled — revoking someone from a folder does not un-issue the link they already hold, and the signature’s lifetime is the only bound on that window. Encrypting blocks on the client before they leave changes the shape again: the edge then caches ciphertext, which dedups fine inside one key scope and not at all across accounts.

What the new pieces do

CDN / Cacheedge
Caches frequently downloaded blocks near users so popular files don't pull from the origin every time.

Step 6 · Scale the metadata

Billions of files

Block storage scales easily, but the metadata — every file, folder, and version for hundreds of millions of users — outgrows a single database.

LaptopPhoneAPI GatewayMetadata ServiceBlock ServiceNotification SvcMetadata DBBlock StoreCDN / CacheSync Events
The system as it stands at this step. · swipe to pan the diagram

Block storage scales easily, but metadata for 100s of millions of users outgrows one DB. How do you partition it?

  1. Random per-file sharding scatters one user’s tree across many shards, so listing a folder or syncing becomes a cross-shard fan-out. Keep an account’s files together.

  2. Caching helps reads but the authoritative metadata still outgrows one machine’s write capacity and storage. You must partition the data itself.

  3. A user’s files are accessed together and rarely cross into another user’s tree, so user id is a clean shard key: even load, operations local to one shard, no cross-shard transactions for everyday ops. Read replicas handle listing-heavy load.

Shard the metadata DB by user (or workspace), so all of one account’s tree lives together and operations stay local to a shard. Add read replicas for listing-heavy workloads. The block store, already object storage, scales horizontally on its own.

Why this piece earns its place

Sharding by user assumes a tree belongs to one person, and the sharing feature one step from here says it does not. The moment a folder is shared, one subtree is reachable from several users’ trees, and those users hash to different shards — so either the folder is duplicated per member and every write fans out across shards, or it lives in one place and a listing crosses shards after all. The cleaner shape is to shard by namespace instead of by person: a folder tree is the unit, an account is a set of mounted namespaces, and the shard key follows the namespace so a shared folder stays whole. The second thing to get right is the mapping itself. Hashing an id modulo the shard count turns adding capacity into a rebalance of everything, and at billions of files that is not an operation anyone gets to run. Keep an explicit lookup table from namespace to shard, so a move is one row and you can drain a hot shard — the one workspace far larger than any normal account — a namespace at a time.

  • 100s of Musers
  • billionsfiles
  • shardby user

Back of the envelope

shard metadata by user id
a tree lives on one shard
everyday ops = single-shard
no cross-shard transactions
+ read replicas
for listing-heavy load

Step 7 · The sharp edges

Conflicts, offline & sharing

Two devices edit the same file while one is offline. Whose version wins? And how do permissions work when a folder is shared with a team?

LaptopPhoneAPI GatewayMetadata ServiceBlock ServiceNotification SvcMetadata DBBlock StoreCDN / CacheSync Events
The system as it stands at this step. · swipe to pan the diagram

Track versions and detect divergence; rather than silently losing data, keep both as a conflicted copy and let the user reconcile. Queue changes made offline and replay them on reconnect. Layer sharing and permissions onto the metadata tree so access is checked on every operation.

Why this piece earns its place

A conflicted copy answers the easy half. Two devices edit the same bytes, keep both, done — but a device returning after a week offline is replaying namespace operations too, and a rename, a move or a delete has no second version to keep. Someone deleted the folder your queued edit belongs in; someone renamed the parent you are moving a child into; two devices moved directories into each other and replaying both honestly produces a cycle. So the offline queue cannot be a bag of file writes: each operation carries the version it was made against, so the server can tell a clean replay from a real divergence, and namespace operations are applied server-side in an order the server picks against the tree as it exists now. Where the outcome is genuinely ambiguous, choose the recoverable mistake — resurrect the file inside the deleted folder rather than let the delete win, because a user can throw away a copy they did not want and cannot recover bytes you dropped. And watch the blast radius: a conflicted copy inside a shared folder syncs to everyone in it.

You did it

You just designed Dropbox.

LaptopPhoneAPI GatewayMetadata ServiceBlock ServiceNotification SvcMetadata DBBlock StoreCDN / CacheSync Events
The finished design, end to end. · swipe to pan the diagram

Everything you assembled, in order

  • Split metadata (the file tree) from content (the bytes).
  • Chunk files into content-addressed blocks and deduplicate.
  • Delta sync moves only the blocks that actually changed.
  • A notification service pushes change events; devices then pull.
  • A CDN caches immutable hot blocks for fast downloads everywhere.
  • Shard the metadata DB by user to scale to billions of files.
  • Versioning, conflict copies, offline queues and sharing for real life.

Where an interviewer pokes next

Getting the boxes right is the easy half. These are the questions that separate a candidate who drew the diagram from one who has run the thing. Answer each one out loud before you open it.

  1. Fixed-size vs content-defined chunking — does the boundary matter?

    Hugely. With fixed-size blocks, inserting one byte near the start shifts every subsequent boundary, so every block hash changes and dedup/delta-sync collapses to "re-upload everything." Content-defined chunking (a rolling hash / Rabin fingerprint picks boundaries based on content) keeps boundaries stable across insertions, so an edit only changes the blocks it actually touches. Real sync engines use content-defined chunking for exactly this reason.

  2. How are edit conflicts from two devices resolved?

    Versioning plus divergence detection. Each change is a new version pointing at block hashes; if two devices branch from the same base and both commit, the server detects the conflict and — rather than silently overwrite — keeps both as a "conflicted copy" for the user to reconcile. The cardinal rule of sync is that no edit silently disappears; predictable-and-safe beats clever-and-lossy.

  3. How does offline editing work?

    The local client queues changes made offline (new blocks + metadata ops) and replays them on reconnect, syncing its queued ops against whatever changed on the server meanwhile — the same merge/conflict machinery as online sync, just delayed. Because content is content-addressed blocks and the namespace is versioned, "catch up" is computing the block/metadata diff since the device’s last-known version.

  4. Why sync metadata first, then bytes?

    Metadata is tiny and tells each device exactly what changed (which paths, which block hashes) without moving any content. A device pulls the new file tree, computes which blocks it’s missing, and fetches only those — often none, if the blocks are already present locally or shared. Leading with metadata makes sync fast and lets a device decide what, if anything, it actually needs to download.

  5. How do you keep dedup safe — hash collisions or privacy?

    Use a strong cryptographic hash (e.g. SHA-256) so accidental collisions are astronomically unlikely, and treat the hash as the block’s identity. Cross-user dedup needs care: naive global dedup can leak "does this file already exist?" (a confirmation oracle) and complicates encryption, so privacy-sensitive systems scope dedup per-user/workspace or dedup over client-encrypted blocks. The trade is storage savings vs information leakage.

Check yourself — the answers, and why

Eight steps in, these are the calls you should be able to make cold. Pick one, then read why.

  1. A file is modeled as…

    Split the small mutable metadata from the immutable bytes; sync metadata first, fetch missing blocks.

  2. Content-addressing a block by its hash gives you…

    A block named by its content is stored a single time, across files and users.

  3. Delta sync moves only changed bytes by…

    Files are lists of block hashes, so "what changed" is a hash set difference — any file type.

  4. Other devices learn of a change via…

    Lightweight push wakes the right devices; the heavier pull happens on demand off an event stream.

  5. A CDN scales downloads almost for free because blocks are…

    A block named by its hash can never change, so it caches anywhere with zero invalidation logic.

How you’d open this design in an interview

Before any boxes: agree what it must do, pin the qualities that shape everything, then build — naming each trade-off as you make it. The walkthrough above is that exact order.

What it must do

Agree on these before drawing a single box.

  • Sync a change: drop or edit a file on one device and have it appear on every other device on the account.
  • Move only the delta: edit one paragraph of a 2 GB file and only the changed blocks travel, not the whole file.
  • Deduplicate: a block shared by a thousand users is stored once, globally.
  • Push, don’t poll: tell other devices the instant something changes instead of asking every few seconds.
  • Real life: versioning, conflict copies, offline queues, and shared-folder permissions.

The qualities that shape everything

Each one names the mechanism that buys it.

Sync "what changed" cheaply
Split the tiny mutable metadata (file tree, names, versions) from the giant immutable bytes; devices sync the small metadata first to learn exactly what changed, then fetch only the bytes they lack.
Never store the same bytes twice
The Block Service splits files into fixed-size blocks, names each by the hash of its contents, and stores a block once globally — if the hash already exists, skip the upload.
Minimal bandwidth per save
On a change, re-chunk and set-difference the new block hashes against the old list, uploading only the genuinely new blocks; the new version mostly points at existing ones.
Instant multi-device sync without polling storms
A Notification Service holds a long-lived connection per device and pushes a nudge off a sync-event stream; woken devices pull metadata and missing blocks, so the writer never needs to know who is online.
Fast downloads of hot files everywhere
A CDN caches immutable, content-addressed blocks near users — they can never change, so they cache forever with zero invalidation; the origin only serves misses and the long tail.
Scale metadata to billions of files
Shard the metadata DB by user (or workspace) so an account’s whole tree lives on one shard and everyday operations stay single-shard, with read replicas for listing-heavy load.

The trade-offs you say out loud

Senior signal isn’t the boxes — it’s naming what you gave up and why it was the right price.

Split metadata from content over storing the whole file as one object

Conflating the tiny mutable metadata with the giant immutable bytes means every rename rewrites bytes and you can’t sync "what changed" cheaply. Splitting them lets devices sync the small file tree first, then fetch only the bytes they lack.

Content-addressed blocks over compressing and re-sending the whole file

Compression shrinks one upload but you still re-send the entire file on any change and store identical files separately. Naming each block by its content hash stores identical data once globally, so a block whose hash already exists is skipped.

Push notifications, then pull over each device polling every few seconds

Millions of devices polling is laggy — you wait a poll interval — and floods servers with empty checks. A lightweight push wakes only the affected devices instantly; the heavier metadata-and-block pull happens on demand off the event stream.

CDN caching of immutable blocks over adding read replicas to the block store

Replicas add origin throughput but distant users still pay the long round-trip and you re-serve the same hot block repeatedly. A content-addressed block can never change, so caching it at the edge near users needs zero invalidation and scales downloads almost for free.

Shard metadata by user over sharding randomly by file id

Random per-file sharding scatters one user’s tree across many shards, turning a folder listing or sync into a cross-shard fan-out. A user’s files are accessed together, so user id is a clean shard key — even load and single-shard everyday operations.

The answer, out loud

What a strong answer to “Design Dropbox (File Sync)” sounds like, first question to last trade-off. It is about 7 minutes of talking; the whiteboard and the interviewer fill the rest of the 45. Read it aloud once, then close the page and give it yourself.

  1. 0–3 min

    Pin down what sync has to do

    Let me pin down what we’re building before I draw anything. Someone drops a file into a folder on one device and it should show up on their other devices, and on anyone they’ve shared it with, and when they edit one paragraph of something huge, only that paragraph should travel. So I’m optimising bandwidth and time-to-appear. The constraint I’d put on the table now is that nothing may be silently lost — devices go offline, edit the same file and come back, and I’d rather hand someone a duplicate than quietly drop their work.

  2. 3–8 min

    A file is two different things

    The first decision is what a file even is, and I’d say it’s two things that shouldn’t live together. There’s the tree — paths, names, versions, who can see it — which is tiny and changes constantly. And there are the bytes, which are enormous and, once written, never change. Store them as one object and renaming a file rewrites the file, and a device has no cheap way to ask what changed. So a metadata service owns the tree, content lives elsewhere, and an upload becomes: store the bytes, then record that this path at this version is made of those bytes. Devices sync the tree first, because the tree tells them what to fetch.

    Built in step 1: Files = metadata + bytes
  3. 8–15 min

    Cut files into blocks and name them by their contents

    Now the bytes. I don’t want to move or store a file as a unit, because two things go wrong. Editing one slide of a two gigabyte file re-sends two gigabytes, and if a thousand people share the same attachment I store it a thousand times. So the client cuts every file into blocks — say four megabytes — hashes each one, and the block’s name is that hash. A file’s metadata is then an ordered list of hashes. Before uploading, the client asks which hashes the server already has and sends only the ones missing. That buys deduplication across every file and every account, because identical bytes produce an identical name no matter who typed them.

    Built in step 2: Don’t move the whole file
  4. 15–21 min

    What changed is a set difference

    Delta sync falls straight out of that. On a save the client re-chunks the file, compares the new list of hashes against the old, and uploads the difference — usually a handful of blocks out of thousands. The new version in metadata is mostly pointers to blocks that already exist. Downloads are the same in reverse: a device pulls the new block list, subtracts what it has on disk, and fetches only the gap, often nothing at all. And because the diff is over hashes rather than text, it behaves identically for a document, a video or a zip.

    Built in step 3: Send only what changed
  5. 21–28 min

    Tell the other devices instead of asking

    Next, the other devices have to find out. I don’t want them polling, because polling is both late and expensive — you wait out the interval, and meanwhile hundreds of millions of accounts ask a question whose answer is almost always no. So when the metadata service commits a change it emits an event, and a notification service holding a long-lived connection to each online device pushes a nudge to the ones that care. The nudge is deliberately tiny: something under your account changed. The device then pulls metadata and any missing blocks. Push for the signal, pull for the payload — and the writer never has to know who is online.

    Built in step 4: Tell the other devices, now
  6. 28–34 min

    Downloads are where immutability pays

    Downloads scale the easiest, and it’s because of a decision I already made. A block is named by its content, so it can never change — so I can cache it anywhere, forever, with no invalidation story at all. I put a CDN in front of the block store, and when a file goes round a company the first request in each region pulls from origin and everyone after is served at the edge. Origin handles misses and the long tail. That’s the bulk of my download traffic gone for very little design.

    Built in step 5: Popular files, everywhere
  7. 34–39 min

    Where it stops fitting on one box

    On scale, the blocks are the easy half — that’s object storage and it grows sideways on its own. Metadata is what outgrows a single database, because it’s billions of files behind hundreds of millions of users. I’d partition it so everything one account touches sits together: a whole tree on one shard, so listing a folder or running a sync is one shard’s work, never a fan-out. Reads dominate — every device listing every folder — so read replicas go behind the listing path and the primary keeps the commits.

    Built in step 6: Billions of files
  8. 39–43 min

    The messy parts people actually hit

    Then the real-world edges, where a sync product lives or dies. Every change makes a new version, so history is free and a bad overwrite is recoverable. When two devices branch from the same version and both commit, I keep both — one becomes a conflicted copy with the device’s name on it — instead of picking a winner. Offline devices queue their changes and replay them on reconnect through that same path. Sharing hangs off the metadata tree, so permission is checked on every operation rather than being a property of the bytes.

    Built in step 7: Conflicts, offline & sharing
  9. 43–45 min

    The trade, and what I’d do next

    So: metadata split from content, content-addressed blocks for dedup, a hash set difference for delta sync, push then pull for freshness, a CDN for downloads, and metadata sharded by account. The trade I’d name is that I bought all of it with immutability — nothing changes in place, which is what makes caching and dedup nearly free, and the bill arrives as garbage to collect, history to prune, and a conflict I have to show the user rather than resolve. With more time I’d work through the chunking boundaries properly, and the end-to-end encryption story, because encryption is what pressures cross-account dedup.

What this teaches

Learn system design by building a file sync service like Dropbox step by step. An interactive guide covering metadata vs content, content-addressed block chunking and deduplication, delta sync, push notifications for multi-device sync, CDN block caching, sharding metadata by user, and conflicts/offline/sharing.

Key takeaways

  • Split metadata (the file tree) from content (the bytes).
  • Chunk files into content-addressed blocks and deduplicate.
  • Delta sync moves only the blocks that actually changed.
  • A notification service pushes change events; devices then pull.
  • A CDN caches immutable hot blocks for fast downloads everywhere.
  • Shard the metadata DB by user to scale to billions of files.
  • Versioning, conflict copies, offline queues and sharing for real life.

Concepts covered

  • How does Dropbox sync your files?
  • Files = metadata + bytes
  • Don’t move the whole file
  • Send only what changed
  • Tell the other devices, now
  • Popular files, everywhere
  • Billions of files
  • Conflicts, offline & sharing
built to be synced, not memorized — make the calls, cut the notifier, run the gauntlet.
Finished this one? 0 / 65 System Designs done
NextDesign a Key-Value Store Closest system design to this one

Explore the topic

See this alongside everything else on the same subject — handbooks, system designs, challenges and tools, in one place.

More System Designs