Design Dropbox (LLD)
Files, folders, versioning, chunking, and sync-state modelling for a file-hosting client.
Problem Statement
Design the low-level object model for Dropbox-style file sync and storage. The system should represent files and folders as a tree, split file contents into chunks, keep immutable file versions, synchronize local and remote copies, resolve conflicts, manage sharing permissions, and notify connected clients when something changes.
Focus on the LLD view: domain classes, responsibilities, relationships, and extension seams. You are not designing global metadata sharding, object storage internals, or the public REST API.
Business context
Dropbox is a strong expert-level LLD problem because it combines several interview themes in one domain: a Composite file tree, Memento snapshots for version history, Observer notifications for clients, and a Strategy seam for conflict resolution.
Interviewers use this problem to test whether you can separate storage structure from sync policy. A good answer makes versioning the source of truth, then lets the sync engine compare local and remote histories and publish changes without hard-coding a single conflict rule.
Functional Requirements
Model folders containing files and other folders using a uniform file-system item abstraction.
Split each file version into fixed-size chunks so storage and transfer can happen at chunk granularity.
Keep immutable file versions and allow restoring an older version as a new current version.
Detect local-only, remote-only, and concurrent local-plus-remote changes during sync.
Resolve sync conflicts through a pluggable strategy such as keep latest, keep local, or keep remote.
Notify observers such as desktop clients or mobile clients whenever files, folders, or synced copies change.
Share files or folders with collaborators using read, write, and owner permissions.
Expose a small demo flow that creates a tree, writes versions, shares a file, and runs a conflict-aware sync.
Non-Functional Requirements
Correct version history
A version is immutable once created. Restoring an older version appends a new version instead of mutating history, preserving auditability.
Extensibility of sync policy
Conflict resolution must be a strategy. The sync engine detects the situation; policy decides which side wins.
Efficient transfer boundary
File content is represented by chunks so a production implementation can deduplicate, hash, and transfer only changed chunks.
Client notification
Changes should flow through observers so UI clients, background daemons, and audit listeners subscribe without being embedded in domain logic.
Permission safety
Sharing state is centralized in a service so callers consistently answer read/write questions before exposing content or allowing edits.
Requirement Clarification
QDo we need to design the distributed storage backend?
No. Model the LLD classes a client or metadata service would use. Object storage, replication, and global sharding are HLD concerns.
QShould deletion, move, and rename be fully implemented?
Rename and folder add/remove are part of the model. Full tombstone-based deletion sync can be discussed as an extension.
QHow should conflicting edits be handled?
The base design detects that both local and remote changed from the last synced checksum, then delegates the winner choice to a conflict strategy.
QAre permissions inherited from folders?
The base implementation stores direct grants per item. Production inheritance is an extension that walks ancestors or materializes effective ACLs.
QDo observers receive every low-level chunk event?
No. Observers receive domain-level events such as file updated, folder changed, and conflict resolved. Chunk internals remain encapsulated.
UML Class Diagram
Sequence Diagram
Entity Identification
FileSystemItem
Abstract base for both files and folders. Carries identity, name, parent link, modified time, path calculation, and observer registration.
File
Leaf node in the Composite. Accepts writes, turns content into chunks, appends immutable versions, exposes the current version, and restores old versions by appending a new snapshot.
Folder
Composite node that owns children and computes aggregate size by summing child sizes. Folder changes also emit observer notifications.
FileVersion
Memento snapshot of one file state. Stores version number, author, creation time, chunks, checksum, and reconstructs readable content.
FileChunk
Immutable byte range of a file version. Provides chunk splitting and defensive copies so version state cannot be mutated by callers.
SyncEngine
Compares local and remote checksums against the last synced checksum, decides whether to upload, download, no-op, or resolve a conflict, and records the new synced state.
ShareService
Centralizes collaborator grants and answers permission checks for read and write access.
ChangeObserver
Observer contract used by clients and listeners. It receives a change event whenever an item updates or a child change bubbles up the tree.
Design Patterns Used
FileSystemItem is the component, File is the leaf, and Folder is the composite. Callers can compute size, paths, and observer behavior uniformly across the tree.
FileVersion is an immutable snapshot of file state. Restoring a version creates a new snapshot, so history remains auditable and undo does not mutate the past.
FileSystemItem maintains observers and emits ChangeEvent objects when files or folders change. Clients react without being hard-wired into the domain objects.
SyncEngine detects sync states but delegates conflict choice to ConflictResolutionStrategy. Keep-latest, keep-local, and manual-merge policies become interchangeable classes.
Step-by-Step Design
1Start with a Composite file tree
Make FileSystemItem the common abstraction. Folder stores children and File stores versions, but both expose identity, path, size, and observer behavior.
public abstract class FileSystemItem { public abstract long size(); } public class Folder extends FileSystemItem { private final List<FileSystemItem> children = new ArrayList<>(); public long size() { return children.stream().mapToLong(FileSystemItem::size).sum(); } }2Represent every write as a version memento
A file write chunks the new content and appends a FileVersion. The old versions stay immutable, so restore and audit are natural operations.
public FileVersion write(String author, String content) { List<FileChunk> chunks = FileChunk.from(content.getBytes(StandardCharsets.UTF_8), chunkSize); FileVersion version = FileVersion.create(history.size() + 1, author, chunks); history.add(version); markUpdated("file-updated:v" + version.number()); return version; }3Keep chunks immutable and independently hashable
The sample keeps one checksum per version, but chunk objects are already independent immutable byte ranges. A production version can add per-chunk hashes and deduplication without changing the file tree.
4Make sync detection separate from conflict policy
SyncEngine compares local, remote, and last synced checksums. If both changed and differ, it calls a ConflictResolutionStrategy instead of embedding a policy.
if (localChanged && remoteChanged) { File winner = conflictResolutionStrategy.chooseWinner(local, remote); File loser = winner == local ? remote : local; copyContent(winner, loser, "conflict-resolution"); return new SyncResult(SyncAction.CONFLICT_RESOLVED, "Conflict resolved."); }5Notify observers from the domain boundary
Writes, restores, folder adds, and folder removes call markUpdated. The event is sent to observers and bubbles to the parent folder so a root-level client can watch the whole tree.
6Keep sharing outside storage objects
ShareService owns grants by item id. That avoids polluting File and Folder with collaboration rules and gives one place to enforce read/write semantics.
Complete Java Implementation
Explanation of Every Class
FileSystemItem
The abstract Composite component. It stores common metadata, computes paths through the parent link, owns observer registration, emits ChangeEvent, and bubbles child changes to parent folders.
File
The leaf node. write chunks content and appends a FileVersion; restoreVersion appends a copied memento so history stays immutable.
Folder
The composite node. It owns child items, supports add/remove, implements iteration, and computes size by summing children.
FileVersion
The Memento. It is immutable, captures author/time/chunks/checksum, reconstructs content, and exposes checksum for sync comparisons.
FileChunk
Immutable chunk value object. It splits byte arrays into fixed-size chunks and returns defensive byte copies.
SyncEngine
The synchronization coordinator. It compares local and remote checksums with the last synced checksum, then uploads, downloads, no-ops, or delegates conflict selection to ConflictResolutionStrategy.
ShareService
Permission facade for collaboration. It stores item grants and answers canRead/canWrite based on read, write, and owner levels.
Main
A runnable demo that builds a tree, registers an observer, writes and restores file versions, grants write access, and triggers a conflict-aware sync.
Dry Run
Sample input
Folder /root/projects contains roadmap.txt with chunk size 8. Alice writes v1 and v2, restores v1, shares write access with Bob, then Alice and Bob both edit before the next sync.
| Step | Action | Version state | Sync state | Observer or permission result |
|---|---|---|---|---|
| 1 | Alice writes v1 | roadmap v1 has chunked content | last synced empty | Root observer sees file-updated:v1 |
| 2 | Alice writes v2 | roadmap v2 becomes current | last synced empty | Root observer sees file-updated:v2 |
| 3 | Alice restores v1 | roadmap v3 copies v1 content | last synced empty | History still keeps v1 and v2 |
| 4 | Share with Bob | versions unchanged | last synced empty | Bob canWrite is true |
| 5 | Alice edits local and Bob edits remote | local v4 and remote v4 diverge | both differ from last synced | SyncEngine calls KeepLatestStrategy |
| 6 | Conflict resolved | loser receives winning content as a new version | last synced checksum updated | Observers receive sync write notification |
The dry run highlights the key interview point: versions are never mutated. Even restore and conflict resolution create new versions, and observers see each domain-level change.
Complexity Analysis
| Operation | Time | Space | Note |
|---|---|---|---|
| write file | O(N) | O(N) | N is content size. The write splits bytes into chunks and stores an immutable version. |
| restore version | O(V) | O(1) | V versions are scanned by number; chunks are shared because they are immutable. |
| folder size | O(T) | O(H) | T items in the subtree, H recursion/iterator depth conceptually. The sample uses stream traversal over children. |
| sync file | O(N) | O(N) | Checksum lookup is O(1), but copying winning content creates a new version from N bytes when upload/download/conflict occurs. |
| permission check | O(1) | O(1) | Direct item grants are stored in hash maps. |
The reference implementation is intentionally readable. Production Dropbox would avoid copying whole content on sync by comparing chunk hashes and transferring only missing chunks.
Extensibility
New conflict policy
Implement ConflictResolutionStrategy for keep-local, keep-remote, keep-latest, manual merge, or create-conflicted-copy. SyncEngine does not change.
Chunk deduplication
Add per-chunk hashes and a ChunkStore repository. FileVersion can store chunk ids instead of byte arrays while keeping the same Memento shape.
Permission inheritance
Enhance ShareService to evaluate ancestor folders or cache effective ACLs. Files and folders still remain storage objects, not policy engines.
Deletion sync
Introduce tombstone versions and deletion events. Sync can then compare create/update/delete histories instead of only content checksums.
Multiple client observers
Observers can be UI refreshers, activity feeds, audit loggers, or websocket publishers. Domain events remain stable.
Alternative Designs
Repository-backed metadata model
Move file, folder, version, and share state behind repositories so the sync engine works against persistent metadata rows.
Tradeoffs
Closer to production and easier to scale across processes, but heavier for an LLD interview and less focused on object collaboration.
Event-sourced version history
Store every write, restore, share, rename, and conflict as an append-only event stream, then rebuild current state from events.
Tradeoffs
Excellent auditability and replay, but much more complex than immutable FileVersion mementos for the base problem.
Manual conflict branch
Instead of choosing a winner, create a second file such as roadmap conflicted copy and notify the user to merge.
Tradeoffs
Safer for user data, but sync no longer converges automatically and clients need extra UX for conflict files.
Common Mistakes
- ×
Storing versions as mutable references to the current file content, which makes restore and audit incorrect.
- ×
Putting sync conflict policy directly inside File or Folder instead of keeping it in SyncEngine plus a strategy.
- ×
Treating folders and files as unrelated classes, causing duplicated path, size, and observer logic instead of using Composite.
- ×
Notifying clients from UI code only, so background sync and programmatic writes never publish events.
- ×
Over-designing distributed storage before the file tree, versioning, and sync state are clear.
- ×
Mixing sharing grants into file content classes, which makes storage and authorization hard to evolve independently.
- ×
Resolving concurrent local and remote edits by blindly overwriting one side without detecting a conflict.
Follow-up Interview Questions
QHow would you sync only changed chunks?
Give each FileChunk a content hash and store versions as ordered chunk ids. Sync compares chunk manifests and uploads only missing hashes.
QHow would you support delete and undelete?
Represent deletion as a tombstone version or event. Undelete appends a new version that points back to the last live content snapshot.
QHow do folder permissions inherit to files?
ShareService can walk parent folders to compute effective permission, or maintain a denormalized ACL cache updated on share changes.
QWhat happens if both sides edit offline?
The engine sees local and remote checksums both differ from the last synced checksum. That is a conflict, so it delegates to the configured strategy.
QWhere would websocket or push notifications live?
As observers or observer adapters. They subscribe to domain events and publish externally without changing File or Folder.
Production Considerations
Durable metadata
Persist item ids, parent ids, version manifests, checksums, and grants in a transactional store. In-memory objects become a domain layer over repositories.
Chunk storage
Store chunks by hash in object storage with reference counts. This enables deduplication and avoids re-uploading common blocks.
Conflict safety
Prefer user-safe policies such as conflicted copies for real products. Automatic keep-latest is simple but can surprise users.
Observer delivery
Use durable queues for notifications so client updates are retried and ordered. The in-process observer is the LLD seam, not the distributed transport.
Security and audit
Check permissions on every read/write/share call, audit grant changes, encrypt file chunks at rest, and never expose raw object-store keys to clients.
What Interviewers Look For
Did the candidate identify Composite as the clean way to model folders and files uniformly?
Did they make version history immutable and use restore as a new version rather than mutation?
Can they explain local-only, remote-only, same, and conflict sync states clearly?
Is conflict handling a Strategy instead of a hard-coded branch that will grow over time?
Do observers notify clients from domain changes without coupling the model to UI or networking?
Are permissions centralized so sharing rules do not leak into every file operation?
Quiz
0/5 answered
1.Why is **FileVersion** modeled as immutable?
2.Which pattern best describes **Folder** containing both files and folders through **FileSystemItem**?
3.When should **SyncEngine** invoke **ConflictResolutionStrategy**?
4.What does the Observer pattern buy in this design?
5.Why keep **ShareService** separate from **File** and **Folder**?
Practice Variants
Add conflicted-copy resolution
AdvancedImplement a strategy that keeps both edits by creating a new sibling file named as a conflicted copy, then notifies observers.
Add chunk hash deduplication
ExpertExtend FileChunk with SHA-256 hashes and introduce a chunk store that reuses identical chunks across versions and files.
Add inherited folder permissions
AdvancedMake ShareService compute effective permissions by walking parent folders and resolving direct grants versus inherited grants.
Flashcards
Cheat Sheet
Entities: FileSystemItem, File, Folder, FileVersion, FileChunk, SyncEngine, ShareService.
Patterns: Composite for the tree, Memento for file versions, Observer for client notifications, Strategy for conflict resolution.
Write flow: client writes content → File chunks bytes → FileVersion snapshot appended → observers notified.
Sync flow: compare local checksum, remote checksum, and last synced checksum → upload, download, no-op, or resolve conflict through strategy → write winning content as a new version.
Sharing: ShareService stores direct grants and answers canRead/canWrite. Inheritance and ACL caching are extensions.
Invariants: old versions are immutable; restore appends; chunks are defensively copied; conflicts are detected before overwrite; observers publish domain-level events.
References
- BookDesigning Data-Intensive Applications — Martin Kleppmann
- BookDesign Patterns: Elements of Reusable Object-Oriented Software — Gamma, Helm, Johnson, Vlissides
- BlogDropbox Tech Blog — Dropbox Engineering