Design Dropbox
Patterns, Technologies, and Concepts
Key Takeaways
Requirements
You initially framed resumable uploads as a feature rather than a core non-functional requirement, but corrected this in your second attempt and finished with a strong, well-rounded NFR section.
-
Resumable uploads are a non-functional requirement for large file storage systems, not just a feature. If a network failure interrupts a large upload, users must be able to resume from where they left off rather than restarting. This is critical for reliability and should be called out explicitly as a system requirement alongside availability and latency.
-
When stating latency requirements, anchor them to a specific number to make them actionable. For a file sync system like Dropbox, a commonly accepted target is syncing changes across devices within roughly one minute. Vague statements like ‘low latency’ are harder to design around than concrete targets.
Core Entities
You initially missed the Directory entity but corrected it on your second attempt, showing good adaptability when given feedback on a core structural gap.
-
In file storage systems like Dropbox, Directory should be a first-class entity in your data model, not just an implied grouping. Directories have their own metadata, can be nested, and are the unit of sync across devices, meaning changes to a directory need to propagate to all linked devices.
-
Separating File and FileMetadata is a strong pattern in distributed systems because file content (the raw bytes) is typically stored in blob/object storage like S3, while metadata like name, size, and timestamps lives in a relational or NoSQL database for fast querying and indexing.
API
You demonstrated a strong grasp of API design fundamentals across both attempts, with your main growth area being adding metadata and pagination to sync responses rather than just returning raw change events.
-
When designing a sync endpoint that returns change events, always include file metadata in the response alongside the event type. Clients need to know not just that a file changed, but what changed about it, such as the new file name, size, or last modified time, so they can update their local state without making additional requests.
-
Cursor-based pagination is preferred over offset-based pagination for event or feed style endpoints. With offset pagination, if new events are inserted while a user is paginating, they can miss or see duplicate records. A cursor tied to the last seen event ID or timestamp avoids this problem and is more efficient for large datasets.
-
For a sync endpoint using a timestamp-based ‘since’ parameter, always support pagination because users who have been offline for a long time could have thousands of change events queued up. Returning all of them in one response risks timeouts and memory issues on both the server and client.
High Level Design
You demonstrated a strong grasp of the core upload and download flows from the start, and you improved steadily across attempts by adding details like upload status tracking, local metadata persistence, and incremental remote sync.
-
A pre-signed URL should never be stored as durable metadata in your database. It is a short-lived token, not a permanent record. Instead, store the S3 object key and generate the pre-signed URL on demand when a client needs to upload or download. This keeps your metadata clean and focused on long-term file state.
-
A client-side sync agent needs a persistent local database, not just an in-memory queue. This local DB should store fields like file path, remote file ID, last modified time, and sync status. Without it, the agent loses track of pending uploads across restarts and cannot efficiently detect what has already been synced.
-
For remote sync, use a ‘changes since timestamp’ API pattern instead of re-listing all files. The client sends a since parameter with its last sync time, the server returns only changed file IDs from the metadata store, and then the client fetches pre-signed download URLs for each changed file. This makes sync incremental and avoids unnecessary data transfer.
-
Close the sync loop explicitly on both ends. After a successful upload, the file service should update the remote metadata record to mark the file as completed. The sync agent should also update the local DB record to mark it as synced. This two-sided update is what keeps the local and remote views consistent and lets the agent recover cleanly after a restart.
Deep Dives
You demonstrated a strong grasp of S3 multipart upload throughout all four attempts, with your answers progressively getting more precise on the finer coordination details like ETags, resume flows, and which actor owns the final completion call.
-
In S3 multipart upload, each UploadPart call returns an ETag for that chunk. The client must collect all ETags and pass them together in the CompleteMultipartUpload call. Without this list of ETags, S3 cannot assemble the final file, so the client must track both the upload ID and the ETag list throughout the upload.
-
The actor that calls CompleteMultipartUpload matters and should be explicit in your design. Either the client calls it directly after all parts succeed, or the client signals your backend which then calls it. Pick one and state it clearly, because interviewers will ask who owns that final step.
-
For resumable uploads, the client should reuse the existing upload ID rather than starting a new multipart session. Call S3 ListParts with the existing upload ID to get an authoritative list of which chunks already landed in S3, then only send the missing ones. This avoids trusting potentially stale client-side state.
-
When explaining a multi-step flow like multipart upload, your whiteboard diagram should match your verbal explanation exactly. If you describe the file service returning multiple pre-signed URLs for individual parts, draw that. Mismatches between your diagram and your words make the design harder to follow and signal unclear thinking to the interviewer.