Common Problems · Handling Large Blobs

Designing a File Storage Service Like Dropbox

Build a system that lets users upload, download, share, and automatically sync files across devices — cheaply, reliably, and even when a single file is 50 GB. The interesting engineering hides in three places: how large files move, how you keep two folders consistent, and how you avoid moving bytes through your own servers.

⏱ Interview target: ~35 min 📶 Difficulty: "easy" on paper — deceptively deep 🏢 Asked at Meta, Google, Amazon
Why this problem matters

Evan (ex-Meta staff engineer) has asked this question roughly 50 times. It's officially "on the easier side" and shows up most for mid-level (E4/L4), but it's asked all the way up to staff. It's really a test of whether you can reason through file transfer at scale — bypassing app servers, resuming interrupted uploads, and keeping distributed copies in sync.

1Understanding the Problem

Definition · What is Dropbox

A cloud-based file storage service that lets users store and share files, and access them securely from any device. You keep a copy of each file locally (in a synced folder) and remotely (in the cloud); the system's job is to keep those in agreement.

Functional requirements — the features

Work through these one at a time; that ordering becomes your design plan. The core features:

  1. Users can upload a file from any device.
  2. Users can download a file from any device.
  3. Users can share a file with others and see files shared with them.
  4. Files automatically sync across devices.

Out of scope: editing files, previewing without download, per-user storage limits, versioning, virus scanning — and, importantly, rolling your own blob storage (that's a separate "Design S3" question).

Instructor Insight · Scope drift between the two sources

In the video, Evan defines only three functional requirements — upload, download, sync — and treats sharing as a side note. The written article promotes sharing to a full fourth requirement with its own design ladder. Both are defensible; just be explicit with your interviewer about which features you're committing to build.

2Non-Functional Requirements & the CAP Trade-off

Non-functional requirements are the qualities of the system. Most candidates breeze through these with generic buzzwords — but for senior and staff levels these are exactly what should lead your deep dives later. Phrase each one in the context of this system, and quantify where you can.

  1. Availability > consistency. Prioritize being able to serve files over everyone seeing the very latest version instantly.
  2. Support large files — up to 50 GB (exactly what real Dropbox supports), with resumable uploads.
  3. Secure & reliable — recoverable if files are lost or corrupted; high data integrity / sync accuracy.
  4. Low latency upload, download, and sync — "as fast as possible" (hard to put a single number on, since files vary).
Core Idea · Why availability wins here

You should prioritize consistency over availability only when every read must reflect the most recent write or the system breaks. A stock-trading app is the classic case: if a user buys AAPL in Germany, a buyer in the US must see that trade replicated before proceeding. File storage has no such rule — if someone uploads a file in Germany and a user in the US can't see it for a few seconds, nothing breaks. So we accept eventual consistency and keep the system highly available.

Instructor Insight · Skip the up-front estimations

Evan deliberately does not do back-of-the-envelope math up front. Storage sits on near-infinitely-scalable blob storage (S3), so a headline "wow, that's a lot of data" number doesn't inform any decision. His rule: do a calculation only at the moment its result will directly change your design (you'll see exactly that when we compute upload time for a 50 GB file). Tell your interviewer you're taking this approach — they rarely object.

Instructor Insight · "High data integrity" as an explicit NFR

The video adds sync accuracy / data integrity as a named requirement: eventual consistency is fine and a couple seconds of lag is acceptable, but once things settle, what's in the local folder must equal what's in remote must equal what's in every other folder. This requirement is what later justifies the reconciliation job.

3Core Entities & the API

Core entities

Start broad — you don't need every column yet, just the objects that get persisted and exchanged. For Dropbox they're almost trivial:

  • File — the raw bytes users upload/download/share.
  • FileMetadata — name, size, MIME type, uploader, and (later) the S3 link, status, and chunk list.
  • User — the account. So minor here that the video suggests it can be more distraction than help.

The API — one endpoint per feature (first draft)

Use the core entities as inputs/outputs so you can move fast. Upload and download start simple:

POST /files
Request: { File, FileMetadata }

GET  /files/{fileId} -> File & FileMetadata

Sharing, and a way for clients to discover what changed since they last synced:

POST /files/{fileId}/share
Request: { User[] }              // users to share with

GET  /files/changes?since={timestamp} -> ChangeEvent[]

Each ChangeEvent carries the fileId, the change type (created / updated / deleted), and updated metadata. The since timestamp lets a client fetch only what changed since its last sync.

Watch Out · These APIs are intentionally wrong

Evan flags up front that this first-draft upload API will not survive. You won't actually POST a 50 GB file body through your own server — you'll switch to presigned URLs and chunk uploads. Say this out loud: "I'll outline simple APIs now and revise them as we weigh trade-offs." Most mid-level candidates don't know the final shape yet, and that's fine — the reasoning back-and-forth is the point.

Tip · Keep user identity out of the body

Pass the user via an auth header (session token or JWT), never in the request body. A body value is trivially forged, letting anyone upload or download on another user's behalf.

4High-Level Design

The goal of the high-level design is simply to satisfy the functional requirements — so walk your APIs one by one. For each feature the source builds a Bad → Good → Great ladder; the reasoning between rungs is what an interviewer is grading.

Two storage questions drive everything: where do the raw bytes live, and where does the metadata live? Metadata is loosely structured with a dominant "fetch files by user" query, so a NoSQL store like DynamoDB fits — though PostgreSQL works just as well. A starter metadata document:

{
  "id": "123",
  "name": "file.txt",
  "size": 1000,
  "mimeType": "text/plain",
  "uploadedBy": "user1"
}
Instructor Insight · The SQL vs NoSQL debate is stale

Evan skips arguing SQL vs NoSQL: "these technologies have converged — well-configured, they can largely do the same things." With SQL you'd normalize chunks into a separate table with a foreign key; with DynamoDB you'd embed the chunk list on the item. He picks DynamoDB and moves on. Don't burn interview time here.

4.1 Upload — from local disk to blob storage

Bad · single server

Store files on the app server's disk

The File Service accepts the file + metadata and writes the bytes to its own local filesystem.

Why it failsStorage fills up as files grow, forcing constant scaling; and if the server dies you lose every file. Not scalable, not reliable.

Good · blob storage

Server forwards the file to S3 / GCS

The backend receives the upload and pushes it to blob storage, saving metadata in the DB. Near-infinite scale and durability come for free.

Why it's not enoughYou upload the file twice — once to your server, once to S3 — wasting bandwidth and CPU. You also must handle the partial-failure case (file saved but metadata not, or vice-versa) with a transactional "only commit metadata if the upload succeeded" approach.

Great · direct-to-S3 via presigned URL

Client uploads straight to S3; backend only signs

The POST /files becomes a three-step dance:

  1. Client requests a presigned URL (backend saves metadata with status: "uploading").
  2. Client PUTs the bytes directly to that URL — the file never touches your servers.
  3. S3 fires an S3 Notification to your backend, which flips status to "uploaded".
POST /files/presigned-url -> PresignedUrl
Request: { FileMetadata }

Why it winsFaster and cheaper — bytes flow client→S3 with no redundant hop. This is the classic "Handling Large Blobs" pattern: bypass app servers for data transfer, use signed URLs for security.

Definition · Presigned URL

A URL your backend generates (using its AWS credentials and the S3 SDK) that grants a client permission to upload/download a specific object for a limited time. Generating it is a purely local, cryptographic operation — no call to S3 is made. The signature can pin the allowed MIME type, size, and expiry (e.g. 10–30 min), so the holder can only do exactly what you authorized.

① SINGLE SERVER (bad) Client Server disk ② VIA BACKEND (good) Client Server S3 uploaded twice ✗ ③ DIRECT TO S3 VIA PRESIGNED URL (great) Client FileService S3 blob 1. ask for URL 2. presigned URL 3. PUT bytes directly 4. S3 notification → status:uploaded
The upload path evolves from "server keeps the bytes" to "client talks straight to S3, backend just signs and records metadata."

4.2 Download — get bytes back to the user

Bad · through the server

S3 → backend → client

The file is downloaded once from S3 to your server, then again from your server to the client — slow and expensive (the mirror image of the redundant-upload problem).

Good · direct from S3

Presigned download URL

GET /files/{fileId}/presigned-url returns a time-limited link; the client pulls bytes straight from S3.

Remaining weaknessS3 lives in one region, so users far away get slow downloads.

Great · via CDN

Serve from the nearest edge

A CDN (e.g. CloudFront) caches files close to users. The File Service hands out a CDN signed URL instead of a raw S3 URL; the CDN fetches from S3 on a cache miss and serves from the edge afterward.

The costCDNs are expensive. Use cache-control headers and invalidation so only frequently-accessed files are cached.

Instructor Insight · The CDN might be the wrong call here

This is where the video pushes back on the article. A CDN only pays off when the same file is read many times, or read far from where it's stored. But Dropbox users mostly download their own files, and they're usually near the region that holds them. So the CDN mainly helps for heavily shared or frequently-traveled files. Evan would likely leave the CDN out and note the trade-off — and the article's own comment section agrees, pointing out that a CDN is a cache that only helps read-heavy, publicly-shared files, which are a small fraction of Dropbox traffic.

4.3 Sharing — the fast lookup problem

Sharing works like Google Drive: enter an email, grant access. The design question is how to quickly list the files shared with a user when they open the app.

Bad · sharelist on the file

Add a sharelist array to each file's metadata

Simple to write. But to show "files shared with me," you'd have to scan every file's sharelist — there's no index for the inverse lookup.

Good · inverse cache

Cache a user → files mapping

Keep the sharelist and a cached inverse (user1: ["fileId1","fileId2"]) so "shared with me" is a fast key lookup.

The catchTwo structures to keep in sync — best done by storing both in the same DB and updating them in one transaction.

Great · normalized shares table

A dedicated SharedFiles table

Map userId (partition key) → fileId (sort key). Listing shared files is a single indexed query, and there's no sharelist to keep in sync.

| userId (PK) | fileId (SK) |
|-------------|-------------|
| user1       | fileId1     |
| user1       | fileId2     |
| user2       | fileId3     |

Trade-offSlightly slower than a raw key-value lookup (it's an index query), but eliminates the sync burden — usually worth it.

4.4 Sync — keeping local and remote in agreement

A copy of each file lives locally and in the cloud (the source of truth). Sync runs in two directions:

Instructor Insight · The client is not a small box here

Most system-design diagrams shrink the client to a tiny box. For Dropbox (like Netflix/Spotify/YouTube) there's real logic on the client, so draw it big. It contains: the synced local folder, a downloadable client app, and a local DB that stores metadata (IDs, fingerprints) for files already downloaded — so the client can tell whether a "changed" remote file is one it already has locally.

Local → Remote

A client-side sync agent watches the folder using OS file-system events — FileSystemWatcher on Windows, FSEvents on macOS. On a change it queues the file, calls the upload API with updated metadata, and updates the remote's S3 link + updatedAt. Conflicts resolve by last-write-wins.

Remote → Local

Each client must learn when the server changed so it can pull. The client also cross-references the local DB to avoid re-downloading files it already holds.

Watch Out · The article and the video disagree on notifications

This is the sharpest conflict between the two sources — worth knowing both:

Article: recommends a hybrid — one WebSocket (or SSE) per device for near-instant push, plus periodic polling (GET /files/changes?since=…) as a safety net for dropped connections. "Best of both worlds."

Video: Evan argues adaptive polling alone is appropriate and that WebSockets/long-polling are overkill — a persistent always-on connection per user adds a WebSocket manager and real overhead, and the product doesn't need millisecond freshness. A manual "refresh" button covers the impatient case.

Neither is wrong; it's a genuine product/latency trade-off. Pick a side and state why.

5Tying It All Together

The pieces assembled into one system that satisfies every functional requirement:

Client (app) Synced folder Sync agent (FSEvents) Local DB (fingerprints) chunk · fingerprint · compress (client) LB &API Gateway File Service(signs URLs) Sync Service(get changes) Metadata DB(+ shares, chunks) S3 (raw bytes) direct upload/download (presigned) CDN (optional)debated
Control plane vs data plane: the File Service only signs URLs and records metadata; raw bytes flow client ↔ S3 directly (dashed). The CDN is drawn dashed because whether to include it is a live trade-off.
  • LB & API Gateway — routing, SSL termination, rate limiting, auth. (AWS API Gateway caps request bodies at ~10 MB — remember this number.)
  • File Service — the control plane: reads/writes metadata and signs presigned URLs. It never carries file bytes.
  • Metadata DB — file metadata + the shares table + (later) per-chunk status.
  • S3 — the actual bytes; clients read/write it directly via signed URLs.
  • CDN — optional edge cache for downloads.

6Deep Dive · Supporting Large Files (up to 50 GB)

This is the meat of the problem — where interviewers spend the most time. Two UX goals frame everything: a progress indicator and resumable uploads (don't make someone re-send 49 GB after a dropped connection).

Why a single POST can't work

  • Timeouts — a huge single request blows past client/server timeout windows.
  • Payload limits — browsers, servers, and API gateways cap body size. AWS API Gateway's hard limit is 10 MB and can't be raised.
  • Network interruptions — one drop means starting over.
  • No progress — the user stares at a spinner blind to what's happening.
Example · The math that justifies chunking

A 50 GB file over a 100 Mbps connection:

50 GB × 8 bits/byte ÷ 100 Mbps = 4000 s → 4000 ÷ 60 ÷ 60 ≈ 1.11 hours

An hour-plus with no feedback and no recovery is unacceptable — this is the calculation worth doing in the interview, because its result forces the chunking design.

Core Idea · Chunk on the client, not the server

Break the file into 5–10 MB chunks on the client and upload each directly to S3 (serially or in parallel). Chunking gives you three things at once: a natural progress bar, resumability, and better bandwidth use. A very common mistake is chunking on the server — that defeats the purpose, since you still had to ship the whole file to the server first.

Tracking chunk status for resumability

Store per-chunk state on the FileMetadata item so a resumed upload only sends what's missing:

{
  "id": "123", "name": "file.txt", "size": 1000,
  "mimeType": "text/plain", "uploadedBy": "user1",
  "status": "uploading",
  "chunks": [
    { "id": "chunk1", "status": "uploaded" },
    { "id": "chunk2", "status": "uploading" },
    { "id": "chunk3", "status": "not-uploaded" }
  ]
}

Identifying files & chunks: fingerprinting

Definition · Fingerprint

A hash (e.g. SHA-256) computed over a file's — or chunk's — content. It uniquely identifies content regardless of filename or source, enabling deduplication and resumability. To resume, compare local chunk fingerprints against the fingerprints already marked "uploaded" server-side, and send only the gaps.

Watch Out · Fingerprint identifies content, not the record

Two different users uploading the identical file produce the same fingerprint. So your fileId should be a unique UUID (the record), while the fingerprint is a separate field used only for dedup/resume checks. Don't naively key files by name — different users (or the same user) reuse names constantly.

Keeping chunks in sync with reality

Good · client PATCH

Client reports each success

Client uploads a chunk to S3, gets a success, then PATCHes the backend to mark that chunk uploaded.

The riskYou're trusting the client. A malicious user could mark chunks uploaded without uploading them — corrupting only their own file, but leaving your metadata inconsistent with S3, which is a debugging nightmare.

Great · trust but verify

Server confirms with S3 via ETags

Each chunk gets an ETag on upload. The client includes ETags in its PATCH; the backend verifies them against S3's ListParts API before committing "uploaded." You accept client updates for instant progress feedback, but confirm server-side before finalizing.

Instructor Insight · Why not just use S3 notifications?

You could let S3 Notifications (a form of change-data-capture) tell your backend when a chunk lands, avoiding client trust entirely. The gotcha: with S3's Multipart Upload API, notifications fire only when the whole object is finalized — not per part. So for per-chunk progress you must poll ListParts. Evan lands on trust-but-verify as his interview default, but calls both valid.

Rule · The full large-file upload sequence

1) Client chunks the file (5–10 MB) and fingerprints each chunk + the whole file. 2) Client asks if a file with that fingerprint already exists — if it's "uploading", resume from existing chunk statuses. 3) Otherwise backend calls S3 CreateMultipartUpload, saves metadata as "uploading", returns an uploadId + a presigned URL per part. 4) Client uploads each part, PATCHes status+ETag, backend verifies via ListParts. 5) When all chunks are up, backend calls CompleteMultipartUpload; only after S3 confirms assembly does it mark the file "uploaded".

Extra Context · You don't need chunked downloads

Once CompleteMultipartUpload runs, S3 assembles the parts into one object — downloads then behave like any normal file via a single (CDN or S3) signed URL. For very large files, HTTP Range requests let a client fetch byte ranges in parallel or resume an interrupted download, with no knowledge of the original chunk boundaries.

Tip · Name-drop, but be ready to build it

S3's Multipart Upload feature (and its JS SDK) does exactly this chunking/uploading/assembly for you — mentioning it signals hands-on experience. But you can't get away with only "I'd use multipart upload." Be ready to explain how you'd implement it yourself.

7Deep Dive · Making Upload / Download / Sync Fast

Much of the speed work is already done. To recap the levers, plus two more:

Parallel chunks max out fixed bandwidth

Bandwidth is fixed — the pipe is only so big — but sending multiple chunks in parallel with adaptive chunk sizes uses all the bandwidth available. The same chunking also powers efficient sync: when a file changes, transfer only the changed chunks.

Watch Out · Fixed-size chunks break delta sync

With fixed 5 MB boundaries, inserting one byte near the start shifts every later boundary, so every subsequent chunk gets a new fingerprint — delta sync becomes useless. The fix is Content-Defined Chunking (CDC): boundaries are set by content using a rolling hash (Rabin fingerprinting), so a small edit only touches the chunks around it. This is how real Dropbox achieves efficient delta sync.

Compression — transfer fewer bytes

Compress on the client before upload; decompress after download (keeps the backend out of the data path). But compress selectively:

File typeCompresses?Worth it?
Text / DOCXVery well (5 GB → ~1 GB)Usually yes
Media (JPEG, PNG, MP4)Already compressed — a few % at bestNo — CPU cost outweighs it

Put logic on the client to decide based on file type, size, and network conditions, and record the algorithm in metadata so you can decompress. Common choices: Gzip (universal), Brotli (better text ratios), Zstandard (excellent speed/ratio balance — a strong client-side pick).

Rule · Compress before you encrypt

Always compress before encrypting. Encryption randomizes the bytes, which destroys compressibility — so encrypt-then-compress yields almost no savings.

8Deep Dive · File Security

  1. Encryption in transit — HTTPS everywhere (table stakes).
  2. Encryption at rest — enable S3 server-side encryption; S3 encrypts each file with a key stored separately from the data.
  3. Access control — the shares table/cache is your ACL; only issue download links to authorized users.
Watch Out · What if an authorized user leaks the link?

Someone could post a download link to a public forum. This is why signed URLs expire quickly (e.g. 5 min). But note: a signed URL is a bearer token — anyone holding a valid, unexpired one can download. Expiry limits exposure but doesn't fully prevent sharing. For higher security, add IP binding or require auth cookies alongside the signed URL.

How signed URLs work end-to-end: the server generates a signature over the path + expiry (+ optional IP) using its private key; it's handed to the authorized user; the CDN/S3 validates the signature with the matching public key and checks expiry before serving.

9Deep Dive · Sync Speed & Consistency

Two properties matter: sync should be fast (remote changes appear locally quickly, within reason) and consistent (local and remote eventually match exactly).

Local folder + local DB Remote (source of truth) local changed → upload (FSEvents) remote changed → poll + delta-sync chunks
Sync is bidirectional; the remote copy is authoritative and conflicts resolve last-write-wins.

Making sync fast

Adaptive polling. The client polls for changes, and the interval adapts — poll more often when the app is open or the folder is active, less often otherwise. A manual refresh button forces an immediate poll when needed.

Delta sync. On a change to an existing file, download only the changed chunks (not the whole 50 GB), update the local DB, and let the client app stitch the file back together. A brand-new file has no choice but a full download.

Making sync consistent: two options for "polling"

Option A · poll the DB

Query metadata directly

"For folder 123, give me any file whose chunk changed or was added since my last sync." Simple, effective, and Evan's default choice.

Option B · event bus + cursor

What Dropbox actually does

Push every change event onto an event bus (e.g. Kafka). Each synced folder stores a sync cursor marking the last event it consumed. First sync replays all events to build local state; later syncs read only events after the cursor. Add partitioning by user/folder and periodic snapshots so you don't replay everything forever.

Instructor Insight · The event bus is powerful but overkill here

An event bus with a cursor shines when you need an audit trail, versioning, and rollback — you can roll events forward or back to reconstruct any prior state. But those weren't in our functional requirements. If you overwrite a chunk in the plain-DB approach, the old chunk is gone and you can't roll back — which is fine given our scope. Evan deletes the event bus from his design and opts for simplicity, while flagging that if versioning were required, this is the pattern.

The safety net: reconciliation

Core Idea · Reconciliation catches everything real-time sync misses

Despite best efforts, bugs and dropped connections can leave local and remote inconsistent. Periodically (daily or weekly) the client app fetches all remote info for the folder, compares fingerprints against what's in the local DB and folder, and fixes any mismatch. This is the backstop that ultimately guarantees the "high data integrity" requirement.

Extra Context · Now the API is actually correct

Recall the "wrong" upload API from step 3. It's now fixed: POST /files with metadata → returns a presigned URL; client uploads chunks to that URL; client PATCHes chunk status as it goes. No file body ever passes through your servers.

10What's Expected at Each Level

LevelBreadth / DepthThe bar for Dropbox
Mid (E4/L4) ~80% breadth Clear API + data model; a working high-level design for upload/download/share. Not expected to know presigned URLs or chunking up front — but should reason to them when nudged ("you're uploading twice — how do you avoid that?"). Interviewer may drive the later stages.
Senior (E5) ~60/40 Breeze through the HLD, then spend real time on large-file uploads. Proactively weigh options and land somewhere reasonable. Often can speak first-hand about APIs like multipart upload. Drive 1–2 deep dives.
Staff+ ~40/60 depth Deep dives on the hard scenarios, driven independently — interviewer intervenes only to focus, not steer. May steer toward a topic they find interesting. Fluent on trade-offs, treating the interviewer as a peer. Go deep in 2–3 places.

✦Active Recall

Answer each before expanding. These test reconstruction of the reasoning, not trivia.

Q1Why does Dropbox prioritize availability over consistency, and when would that choice be wrong?

Because no read must reflect the latest write for the system to function — a few seconds of staleness when a file uploaded in one region isn't yet visible in another is acceptable. It would be wrong for a system where every read must see the most recent write, e.g. stock trading, where a share bought in one region must be replicated before a buyer elsewhere can act.

Q2Walk through the three-step direct-to-S3 upload. What problem does it solve versus routing bytes through your backend?

(1) Client requests a presigned URL; backend saves metadata as "uploading." (2) Client PUTs bytes directly to that URL. (3) S3 notifies the backend, which marks status "uploaded." It eliminates the redundant double-upload (client→server→S3), saving bandwidth and CPU, and it removes your servers from the data path entirely.

Q3Why must chunking happen on the client, and what breaks if you do it on the server?

Chunking exists to avoid shipping the whole file at once and to enable resumability/progress. If you chunk on the server, you've already uploaded the entire file to the server to do so — defeating the purpose, and you still hit payload limits and timeouts on that initial transfer.

Q4What is a fingerprint, and why store it separately from fileId?

A content hash (e.g. SHA-256) of a file or chunk, used for dedup and resumability. It identifies content, so two users uploading the same file share a fingerprint. fileId must stay a unique per-record UUID; the fingerprint is a separate field used only for dedup/resume comparisons.

Q5Explain "trust but verify" for chunk status. What's the alternative and its limitation?

The client reports each chunk uploaded (for instant progress), but the backend verifies ETags against S3's ListParts before committing — so a lying client can't corrupt metadata. The alternative is S3 notifications (CDC), which need no client trust, but S3 multipart uploads only fire a notification when the whole object finalizes, not per part, so per-chunk progress still needs ListParts.

Q6Fixed 5 MB chunks make delta sync nearly useless. Why, and what's the fix?

Inserting one byte near the start shifts every later boundary, so every subsequent chunk fingerprint changes and you'd re-transfer almost the whole file. Content-Defined Chunking (CDC) sets boundaries by content via a rolling hash (Rabin), so a small edit only affects nearby chunks.

Q7The article recommends WebSockets + polling; the video prefers polling alone. Reconstruct both arguments.

Article (hybrid): a persistent WebSocket/SSE gives near-instant push; periodic polling is a safety net for dropped connections and missed messages, guaranteeing eventual consistency. Video (polling only): a persistent per-user connection adds a WebSocket manager and overhead the product doesn't need — seconds-level freshness is fine, and adaptive polling + a manual refresh button cover it. It's a latency/complexity trade-off, not a right/wrong.

Q8When is an event-bus-with-cursor worth the complexity over polling the DB, and why did the video drop it?

It's worth it when you need an audit trail, versioning, and rollback — you can replay/roll events to reconstruct any state. The video drops it because those weren't in the functional requirements; overwriting a chunk in the simple DB approach loses history, which is acceptable given the scope. It's what real Dropbox does, but it's overkill for the stated requirements.

Q9Why compress before encrypting, and when should you skip compression entirely?

Encryption randomizes bytes, which are then nearly incompressible — so compress first. Skip compression for already-compressed media (JPEG/PNG/MP4): the gain is a few percent and doesn't justify the compress/decompress CPU time. Text/DOCX compress well and are usually worth it.

✦Quick Revision

Core Ideas

  • Availability > consistency — no read must see the latest write.
  • Bytes bypass your servers — client ↔ S3 via presigned URLs.
  • Chunk on the client → progress, resume, parallel bandwidth.
  • Fingerprints identify content for dedup/resume.
  • Reconciliation is the ultimate integrity backstop.

Numbers & Rules

  • Max file: 50 GB. Chunk size: 5–10 MB.
  • API Gateway body cap: 10 MB (hard).
  • 50 GB @ 100 Mbps ≈ 1.11 hours.
  • Hash: SHA-256. Signed URL expiry: ~5 min.
  • Compress before encrypt. Last-write-wins on conflict.

Bad → Great ladders

  • Upload: server disk → via backend → direct-to-S3.
  • Download: via server → direct S3 → CDN.
  • Share: sharelist → inverse cache → normalized table.
  • Chunk status: client PATCH → trust-but-verify.

Common Mistakes

  • Chunking on the server.
  • Keying files by name instead of fingerprint.
  • Fixed chunk boundaries → broken delta sync (use CDC).
  • Blindly adding a CDN without justifying reads.
  • Generic, unquantified non-functional requirements.

Must Remember · One-line system

The File Service is a control plane that only signs URLs and records metadata; raw bytes flow client ↔ S3 directly. Large files are chunked and fingerprinted on the client, uploaded via multipart with trust-but-verify status, and delta-synced back down. Consistency comes from adaptive polling plus periodic reconciliation.

Study notes compiled from the Hello Interview write-up "Design a File Storage Service Like Dropbox" by Evan King, with instructor commentary integrated from the accompanying video walkthrough. Where the two sources disagree (sync notifications, CDN necessity, scope of "sharing"), both positions are preserved rather than reconciled.