system-design50 min read

How to Design Dropbox File Sync System — A Senior+ Guide | Ayodhyya

How to Design Dropbox File Sync System

A Senior+ Guide — Building conflict-free file synchronization, deduplication, and real-time collaboration at scale

Ayodhyya • July 2026 • 45 min read

System DesignDistributed SystemsSenior+File Sync

1. Introduction

Dropbox is one of the most iconic cloud storage and file synchronization services in the world. Since its founding in 2007, it has grown to serve over 700 million registered users across 180 countries, storing more than exabytes of data. At its core, Dropbox solves a deceptively simple problem: keep files in sync across all of a user's devices. But underneath that simplicity lies one of the most complex distributed systems challenges in modern engineering.

Designing a Dropbox-like file sync system requires mastering several difficult problems simultaneously. You must handle real-time bidirectional synchronization across multiple devices that may be offline for hours or days. You need conflict resolution when two devices edit the same file simultaneously. You must implement content-addressable deduplication to avoid storing the same file content twice. You need block-level delta sync to minimize bandwidth usage. And you need to do all of this while maintaining strong consistency guarantees, sub-second latency, and five-nines availability.

The fundamental challenge of file sync is that the network is unreliable, devices are autonomous, and users expect magical simplicity. — Adapted from the Dropbox engineering blog

In this comprehensive guide, we will walk through the complete system design of a Dropbox-like file sync service. We will cover everything from capacity estimation and data modeling to conflict resolution with CRDTs, multi-region replication, and cost analysis. This guide is designed for senior engineers preparing for system design interviews at top-tier companies, as well as architects building real-world file synchronization systems.

We will reference real-world engineering decisions made by Dropbox, Google Drive, Microsoft OneDrive, and iCloud, while presenting a unified architecture that ties all components together. By the end of this article, you will understand how to design a system that can handle millions of concurrent syncs, petabytes of storage, and complex multi-device conflict scenarios — all while keeping costs manageable and the user experience seamless.

The file synchronization problem is fundamentally different from simple object storage. Unlike storing a static image in S3 and retrieving it later, file sync requires maintaining a living, evolving state machine across multiple clients. Each client operates independently, may go offline at any time, and must be able to rejoin and reconcile its state with the server and other clients. This is why systems like Dropbox use sophisticated distributed systems techniques including vector clocks, operational transformation, content-addressable storage, and conflict-free replicated data types (CRDTs).

2. Requirements

Functional Requirements

  1. File Upload/Download: Users can upload and download files of any size (up to 50 GB) through web, desktop, and mobile clients.
  2. Automatic Sync: Changes made on one device are automatically propagated to all other devices belonging to the same user.
  3. Offline Support: Clients can continue working offline and sync changes when connectivity is restored.
  4. Conflict Resolution: When two devices modify the same file simultaneously, the system must detect and resolve conflicts gracefully.
  5. File Sharing: Users can share files and folders with other users, with configurable read-only or read-write permissions.
  6. Version History: Users can view, restore, and compare previous versions of any file.
  7. Folder Structure: Users can organize files into hierarchical folder structures.
  8. Search: Users can search for files by name, content, or metadata.
  9. Deleted File Recovery: Deleted files can be recovered within a configurable retention period (typically 30 days).

Non-Functional Requirements

  • Availability: 99.99% uptime (approximately 52 minutes of downtime per year).
  • Durability: 99.999999999% (eleven nines) — no data loss under any circumstances.
  • Latency: File metadata operations should complete in under 100 ms. File transfers should begin within 500 ms for files under 10 MB.
  • Consistency: Strong consistency for metadata operations. Eventual consistency for file content replication.
  • Scalability: Support 100 million concurrent active users, 1 billion files, and exabytes of total storage.
  • Bandwidth Efficiency: Minimize data transfer through delta sync and deduplication.

Capacity Estimation

MetricEstimateNotes
Daily Active Users200 million~28% of registered base
Avg files per user500 files1 billion files total
Avg file size1 MBPower-law distribution; most files are small
Total storage1 EB (exabyte)Before deduplication
After deduplication~600 PB~40% dedup ratio
Avg daily uploads per user10 files2 billion uploads/day
Avg daily downloads per user25 files5 billion downloads/day
Upload bandwidth20 PB/day~2.3 Gbps average, but bursty
Read QPS58K5B / 86400s
Write QPS23K2B / 86400s

3. Capacity Estimation (Deep Dive)

A deeper look at capacity helps us make informed architectural choices. Consider that Dropbox handles approximately 2 billion file sync operations per day. With 200 million daily active users, that averages to about 10 sync operations per user per day. However, the distribution is heavily skewed — power users may sync thousands of files per day while casual users sync only a handful.

The storage calculation must account for versioning. If we keep 90 days of version history and the average file has 5 versions, our effective storage before deduplication could be as high as 5 exabytes. Content-addressable deduplication at the block level can reduce this significantly. Dropbox has reported deduplication ratios of 2x to 3x for typical enterprise workloads, which brings our effective storage requirement down to approximately 1.5 to 2.5 exabytes.

For bandwidth estimation, consider that a single 100 MB file modified in 10 different places should not require re-uploading the entire file. Block-level delta sync with 4 MB chunks means only the modified blocks need to be transferred. In practice, users typically modify only 5-10% of a file's content per edit, resulting in a 20x reduction in bandwidth compared to full-file uploads.

ResourceRequirementInfrastructure
Metadata DB (primary)50 TBSharded MySQL cluster
Metadata DB (replica)150 TB (3 replicas)Read replicas across regions
Block Storage2.5 EBCustom file system (S3-compatible)
Cache Layer5 TBRedis / Memcached clusters
Search Index10 TBElasticsearch cluster
Message Queue500 GBKafka cluster (30-day retention)
Compute (sync servers)10K coresKubernetes on bare metal

4. Data Model

The data model is the backbone of the entire system. Dropbox's data model consists of several interconnected entities: users, files, folders, chunks, versions, shares, and sync operations. Let's examine each entity and its relationships in detail.

Entity Relationship Overview

erDiagram USER ||--o{ FILE : owns USER ||--o{ FOLDER : creates USER ||--o{ DEVICE : registers FOLDER ||--o{ FILE : contains FOLDER ||--o{ FOLDER : has FILE ||--o{ FILE_VERSION : has FILE_VERSION ||--o{ CHUNK : contains FILE_VERSION ||--o{ SYNC_OPERATION : triggers FILE ||--o{ SHARE : shared_via SHARE ||--o{ USER : shared_with DEVICE ||--o{ SYNC_OPERATION : performs FILE ||--o{ DELTA_BLOCK : has

User Table

ColumnTypeDescription
user_idBIGINT (PK)Globally unique user identifier
emailVARCHAR(255)Unique email address
nameVARCHAR(255)Display name
storage_quota_bytesBIGINTTotal storage allowance
storage_used_bytesBIGINTCurrent storage consumption
subscription_tierENUMfree, plus, professional, business
created_atTIMESTAMPAccount creation time
is_deletedBOOLEANSoft delete flag

File Table

ColumnTypeDescription
file_idUUID (PK)Unique file identifier
parent_folder_idUUID (FK)Parent folder reference
owner_idBIGINT (FK)File owner
file_nameVARCHAR(512)Current file name
mime_typeVARCHAR(127)MIME type
size_bytesBIGINTFile size in bytes
current_version_idUUID (FK)Latest version reference
is_deletedBOOLEANSoft delete flag
is_folderBOOLEANDirectory vs file flag
deleted_atTIMESTAMPWhen file was soft-deleted
vector_clockJSONCausal ordering metadata
created_atTIMESTAMPInitial creation time
updated_atTIMESTAMPLast modification time

File Version Table

ColumnTypeDescription
version_idUUID (PK)Unique version identifier
file_idUUID (FK)Parent file reference
version_numberINTSequential version number
content_hashVARCHAR(64)SHA-256 of full content
chunk_manifest_idUUID (FK)Reference to chunk manifest
size_bytesBIGINTSize of this version
created_by_device_idUUID (FK)Which device created this version
created_atTIMESTAMPVersion creation timestamp
change_descriptionVARCHAR(255)Human-readable change summary

Chunk Table

ColumnTypeDescription
chunk_idUUID (PK)Unique chunk identifier
content_hashVARCHAR(64)SHA-256 of chunk content (CAS key)
size_bytesINTChunk size (typically 4 MB)
storage_pathVARCHAR(1024)Physical storage location
reference_countINTNumber of versions referencing this chunk
encryption_key_idUUID (FK)Reference to encryption key
created_atTIMESTAMPChunk creation timestamp

Sync Operation Log

ColumnTypeDescription
operation_idUUID (PK)Unique operation identifier
file_idUUID (FK)Affected file
device_idUUID (FK)Originating device
operation_typeENUMcreate, modify, move, delete, rename
vector_clockJSONCausal ordering vector clock
base_version_idUUIDVersion this operation is based on
new_version_idUUIDResulting version after operation
delta_blocksJSONList of changed blocks
statusENUMpending, applied, conflicted, rejected
created_atTIMESTAMPOperation timestamp

5. API Design

The API layer must support both synchronous request-response patterns for metadata operations and streaming patterns for file transfers. We use a combination of REST APIs for CRUD operations and WebSocket connections for real-time sync notifications.

Core REST Endpoints

File Operations

MethodEndpointDescription
POST/api/v2/files/upload/initInitialize a resumable upload session
PUT/api/v2/files/upload/{session_id}/chunk/{index}Upload a single chunk
POST/api/v2/files/upload/{session_id}/commitFinalize upload and create version
GET/api/v2/files/{file_id}/downloadDownload current file version
GET/api/v2/files/{file_id}/download?version={v_id}Download specific version
DELETE/api/v2/files/{file_id}Soft delete a file
PATCH/api/v2/files/{file_id}/moveMove file to new folder
PATCH/api/v2/files/{file_id}/renameRename a file

Metadata & Sync Operations

MethodEndpointDescription
GET/api/v2/files/list?folder_id={id}&cursor={c}List folder contents (paginated)
GET/api/v2/files/{file_id}/versionsList all versions of a file
POST/api/v2/sync/pollPoll for changes since last sync point
POST/api/v2/sync/applyApply local operations to server
GET/api/v2/sync/changes?since={clock}Get all changes since vector clock
POST/api/v2/shares/createShare a file or folder
GET/api/v2/search?q={query}Search files by name or content

WebSocket Protocol for Real-Time Notifications

Clients establish a persistent WebSocket connection to receive real-time sync notifications:

// Connection
ws://sync-stream.ayodhyya.com/v2/stream?token={jwt}

// Server → Client message types
{
    "type": "FILE_CHANGED",
    "payload": {
        "file_id": "uuid-1234",
        "change_type": "MODIFIED",
        "vector_clock": {"device-a": 15, "device-b": 3},
        "changed_by": "user-5678",
        "timestamp": "2026-07-14T10:30:00Z"
    }
}

// Client → Server message types
{
    "type": "SYNC_ACK",
    "payload": {
        "file_id": "uuid-1234",
        "ack_clock": {"device-a": 15, "device-b": 3}
    }
}

6. High-Level Architecture

The Dropbox-like file sync system consists of several major subsystems: the client layer, the API gateway, the sync engine, the metadata service, the blob storage layer, the notification service, and the deduplication engine. Each subsystem is independently scalable and fault-tolerant.

graph TB subgraph Clients C1[Desktop Client] C2[Mobile Client] C3[Web Client] end subgraph "Edge Layer" LB[Load Balancer] GW[API Gateway] WSS[WebSocket Server] end subgraph "Application Layer" SS[Sync Service] MS[Metadata Service] US[Upload Service] DS[Download Service] NS[Notification Service] CS[Conflict Service] SH[Sharing Service] SSRC[Search Service] end subgraph "Data Layer" MDB[(Metadata DB
MySQL Cluster)] CAS[(Content-Addressable
Store / Blob)] CK[(Chunk Store
S3/Custom FS)] RD[(Redis Cache)] KFK[Kafka] IDX[(Search Index
Elasticsearch)] end subgraph "Background Jobs" DD[Dedup Engine] PR[Purger / Retention] MR[Multi-Region Replicator] end C1 & C2 & C3 --> LB LB --> GW LB --> WSS GW --> SS & MS & US & DS & SH & SSRC WSS --> NS SS --> CS SS --> MDB US --> CK US --> DD DS --> CK MS --> MDB MS --> RD SS --> KFK NS --> KFK KFK --> DD & PR & MR CK --> CAS SSRC --> IDX MR --> CK PR --> CAS

The Sync Service is the brain of the system. It coordinates all synchronization operations, resolves conflicts, and maintains the consistency of the file tree. When a client detects a local file change, it uploads the changed chunks to the Upload Service, which writes them to the chunk store. The Upload Service then notifies the Sync Service, which updates the metadata database and publishes change events to Kafka. The Notification Service consumes these events and pushes real-time notifications to all connected clients.

The Metadata Service manages the hierarchical file tree, version history, and sharing permissions. It uses a sharded MySQL cluster for strong consistency, with Redis caches in front to handle read-heavy workloads. The metadata service is the most latency-sensitive component — every file operation requires at least one metadata read and one metadata write.

The Content-Addressable Store (CAS) is responsible for storing file chunks indexed by their content hash. This is the foundation of Dropbox's deduplication system. When two users store the same file, only one copy of each chunk is physically stored. The CAS uses a custom file system optimized for large binary objects, distributed across thousands of storage nodes.

7. Client-Side Architecture

The client-side architecture is arguably the most complex part of the entire system. Each client maintains a local file system watcher, a sync state machine, a local metadata cache, and a network manager that handles online/offline transitions gracefully.

graph LR subgraph "Client Architecture" FW[File System
Watcher] Q[Operation
Queue] SM[Sync State
Machine] LMC[Local Metadata
Cache] NM[Network
Manager] CR[Chunk
Reader] CW[Chunk
Writer] DB[(Local
SQLite DB)] end FW -->|detect change| Q Q --> SM SM --> LMC SM --> NM NM <-->|WebSocket| Server SM --> CR SM --> CW LMC --> DB

File System Watcher

The file system watcher is the entry point of the sync pipeline. On Windows, it uses the ReadDirectoryChangesW API. On macOS, it uses FSEvents. On Linux, it uses inotify. The watcher detects file creation, modification, deletion, rename, and move operations and enqueues them for processing.

A critical challenge with file system watchers is handling burst events. When a user extracts a large ZIP file, the watcher may receive hundreds of file creation events in rapid succession. The client must debounce these events, batching them into a single sync operation to avoid overwhelming the server. Dropbox implements a smart batching algorithm that groups events by directory and applies a configurable debounce window (typically 500ms for individual files, 2 seconds for directory operations).

Sync State Machine

stateDiagram-v2 [*] --> Idle Idle --> Scanning: file change detected Scanning --> Uploading: chunks ready Scanning --> Downloading: server change received Uploading --> WaitingAck: chunks uploaded WaitingAck --> Idle: server acknowledged Downloading --> Applying: chunks downloaded Applying --> Idle: local state updated Idle --> ConflictDetected: version mismatch ConflictDetected --> Resolving: conflict strategy chosen Resolving --> Idle: conflict resolved Uploading --> Offline: network lost Offline --> Uploading: network restored Downloading --> Offline: network lost Offline --> Downloading: network restored

Local Metadata Cache

Each client maintains a local SQLite database that mirrors the server's metadata for all files in the user's sync folder. This local cache enables offline browsing and allows the client to detect changes by comparing local file hashes against the cached hashes. The local database schema mirrors the server's metadata tables but adds client-specific fields like local_path, sync_status, and pending_operations.

The local cache is periodically synchronized with the server using a long-polling or WebSocket connection. When the client comes back online after being offline, it performs a full reconciliation by requesting all changes that occurred since its last known server clock. This reconciliation process handles the complex case where the same file was modified both locally and on the server during the offline period.

8. File Chunking & Deduplication

File chunking and deduplication are the核心技术 that make Dropbox bandwidth-efficient. Without deduplication, uploading a 1 GB file would always transfer 1 GB of data. With content-addressable chunking and deduplication, we can often reduce the transfer to a few kilobytes.

Content-Defined Chunking (CDC)

Dropbox uses a variant of Rabin fingerprinting for content-defined chunking. Unlike fixed-size chunking (which simply splits files at every 4 MB boundary), CDC uses a rolling hash function to find chunk boundaries based on the file's content. This means that inserting a single byte at the beginning of a file does not shift all subsequent chunk boundaries — only the chunk containing the insertion point is affected.

Content-Defined Chunking Algorithm

public class ContentDefinedChunker
{
    private const int MinChunkSize = 1024;       // 1 KB minimum
    private const int MaxChunkSize = 8 * 1024 * 1024; // 8 MB maximum
    private const int TargetChunkSize = 4 * 1024 * 1024; // 4 MB target
    private const uint Polynomial = 0x3DA3358B;  // Rabin fingerprint polynomial
    private const uint WindowSize = 48;           // Rolling hash window

    private readonly byte[] _window = new byte[WindowSize];
    private int _windowPos;
    private long _windowHash;
    private long _chunkSize;

    public List<ChunkInfo> ChunkFile(string filePath)
    {
        var chunks = new List<ChunkInfo>();
        using var stream = File.OpenRead(filePath);
        using var reader = new BinaryReader(stream);

        byte[] buffer = new byte[MaxChunkSize];
        int bytesRead;
        long offset = 0;
        int chunkStart = 0;

        while ((bytesRead = reader.Read(buffer, 0, buffer.Length)) > 0)
        {
            for (int i = 0; i < bytesRead; i++)
            {
                _windowHash = ((_windowHash << 1) + buffer[i]) % Polynomial;
                _window[_windowPos] = buffer[i];
                _windowPos = (_windowPos + 1) % (int)WindowSize;
                _chunkSize++;

                bool isBoundary = (_chunkSize >= MinChunkSize &&
                    (_chunkSize >= MaxChunkSize ||
                     (_windowHash & 0xFFFF) == 0));

                if (isBoundary || i == bytesRead - 1)
                {
                    byte[] chunkData = new byte[_chunkSize];
                    Array.Copy(buffer, chunkStart, chunkData, 0, (int)_chunkSize);
                    string hash = ComputeSHA256(chunkData);

                    chunks.Add(new ChunkInfo
                    {
                        Index = chunks.Count,
                        Offset = offset,
                        Size = _chunkSize,
                        ContentHash = hash,
                        Data = chunkData
                    });

                    offset += _chunkSize;
                    _chunkSize = 0;
                    chunkStart = i + 1;
                }
            }
        }
        return chunks;
    }

    private string ComputeSHA256(byte[] data)
    {
        using var sha = System.Security.Cryptography.SHA256.Create();
        byte[] hash = sha.ComputeData(data);
        return Convert.ToHexString(hash);
    }
}

Deduplication Pipeline

graph TD A[Incoming File] --> B[Content-Defined Chunking] B --> C[SHA-256 Hash per Chunk] C --> D{Chunk exists
in CAS?} D -->|Yes| E[Increment Reference Count] D -->|No| F[Encrypt Chunk] F --> G[Store in Chunk Store] G --> H[Update Reference Count] E --> I[Return Chunk Manifest] H --> I I --> J[Store File Version with
Chunk Manifest]

The deduplication engine runs as a background job that periodically scans the chunk store for chunks with zero references. These orphaned chunks are candidates for garbage collection. The engine must be careful to handle the race condition where a chunk's reference count drops to zero while a new upload is simultaneously referencing it. This is solved using optimistic concurrency control with a reference_count_version field in the chunk metadata.

Dropbox's deduplication operates at two levels. Global deduplication ensures that identical chunks from different users are stored only once. This is the primary source of storage savings. User-level deduplication ensures that a user doesn't re-upload chunks they already have, which is the primary source of bandwidth savings.

9. Metadata Service & File Tree

The metadata service manages the hierarchical file tree — essentially a virtual file system in the cloud. It must support efficient listing, searching, and traversal operations while maintaining strong consistency for concurrent modifications.

File Tree Representation

Dropbox represents the file tree as an adjacency list stored in a relational database. Each file and folder has a parent_folder_id that points to its containing folder. This design enables efficient lookups for common operations (list folder contents, get file metadata) while supporting the full power of SQL for complex queries (search, sharing, version history).

graph TD ROOT["Root Folder
id: root-uuid
owner: user-123"] --> A["Documents/
id: folder-a"] ROOT --> B["Photos/
id: folder-b"] ROOT --> C["Projects/
id: folder-c"] A --> D["resume.pdf
id: file-d
v3"] A --> E["notes.txt
id: file-e
v7"] B --> F["vacation.jpg
id: file-f
v1"] B --> G["sunset.png
id: file-g
v2"] C --> H["design.md
id: file-h
v12"] C --> I["src/
id: folder-i"] I --> J["main.cs
id: file-j
v45"] I --> K["utils.cs
id: file-k
v23"]

Fanout Optimization

A key challenge with the adjacency list model is listing the contents of a folder with millions of files. To handle this, Dropbox uses a materialized path approach combined with a B+ tree index on (parent_folder_id, file_name). This allows listing operations to be served with a single index seek, even for folders with millions of children.

For very large folders (10,000+ items), the metadata service implements cursor-based pagination. The client requests items in batches of 500, using a cursor that encodes the last-seen item's sort key. This ensures that each page request has constant-time performance regardless of the folder's total size.

Metadata Sharding Strategy

graph LR subgraph "Metadata DB Cluster" R1[Router] --> S1[Shard 1
users 0-49M] R1 --> S2[Shard 2
users 50-99M] R1 --> S3[Shard 3
users 100-149M] R1 --> S4[Shard N
users N-M] end subgraph "Each Shard" M[(Primary
MySQL)] R_A[(Replica A)] R_B[(Replica B)] R_C[(Replica C)] end

Metadata is sharded by user_id. All files belonging to a single user reside on the same shard, ensuring that operations within a single user's file tree can be served with single-shard transactions. Cross-user operations (like sharing) require cross-shard queries but are read-heavy and can be served from replicas.

10. Sync Protocol

The sync protocol is the heart of Dropbox's architecture. It must handle the fundamental challenge of distributed synchronization: multiple autonomous clients making concurrent modifications that must be reconciled into a consistent global state.

Operation Log (Oplog)

Every mutation to the file system is recorded as an operation in the operation log (oplog). Each operation contains:

  • Operation ID: Globally unique identifier.
  • Operation Type: create, modify, delete, move, rename.
  • Vector Clock: Causal ordering metadata for the device that created the operation.
  • Base Version: The version of the file this operation was applied to.
  • Payload: Operation-specific data (chunk manifest for modify, new path for move, etc.).
sequenceDiagram participant D1 as Device A participant S as Server participant D2 as Device B D1->>S: poll(since_clock=D1:5, D2:3) S-->>D1: changes=[op_101: modify file_x by D1:6] D1->>S: apply(op_101) S-->>D1: ack(clock=D1:6, D2:3) Note over D2: Device B was offline, now comes online D2->>S: poll(since_clock=D1:5, D2:3) S-->>D2: changes=[op_101: modify file_x by D1:6] D2->>D2: Detect conflict: locally modified file_x D2->>D2: Create conflict copy: file_x (Device B's copy) D2->>S: apply(conflict_resolution) S-->>D2: ack(conflict_resolved)

Vector Clocks

Vector clocks enable the system to determine the causal ordering of operations. Each device maintains a vector clock that tracks the latest known operation from every device. When a device creates a new operation, it increments its own component of the vector clock. The server uses vector clocks to detect whether two operations are causally related (one happened before the other) or truly concurrent (neither happened before the other).

Consider a scenario with two devices, A and B. Device A modifies file X and the server records vector clock {A: 5, B: 3}. Device B was offline and also modifies file X based on the version at clock {A: 4, B: 3}. When Device B's operation arrives at the server, the server detects that {A: 5, B: 3} is not ≤ {A: 4, B: 4} and {A: 4, B: 4} is not ≤ {A: 5, B: 3}. These operations are concurrent, indicating a conflict.

11. Conflict Resolution with CRDTs

Conflict resolution is one of the hardest problems in file sync. When two devices simultaneously modify the same file, the system must decide how to reconcile the changes. Dropbox uses a multi-strategy approach depending on the type of conflict.

Conflict Types and Resolution Strategies

Conflict TypeStrategyUser Experience
Same file, different regions editedLast-writer-wins per region + conflict copyOriginal file + conflict copy with suffix
File deleted on one device, modified on anotherModify wins (undelete)File restored with modification
File moved to different locations on two devicesMove-to-original-location + conflictFile in one location + conflict in other
Concurrent renamesBoth renames applied (last-writer-wins)Single renamed file
Conflicting folder operationsCRDT mergeMerged folder tree

CRDT-Based File Tree

For folder-level operations, Dropbox leverages Conflict-free Replicated Data Types (CRDTs). A CRDT is a data structure that can be replicated across multiple devices and merged without requiring coordination or locking. The key property of a CRDT is that the merge operation is commutative, associative, and idempotent — meaning the order of merges doesn't matter, and merging the same update twice produces the same result.

CRDT File Tree Implementation

public class CRDTFileNode
{
    public string Id { get; set; }
    public string Name { get; set; }
    public DateTime Timestamp { get; set; }
    public string DeviceId { get; set; }
    public NodeState State { get; set; }  // ACTIVE or DELETED
    public Dictionary<string, CRDTFileNode> Children { get; set; }
    public string ParentId { get; set; }

    public enum NodeState { ACTIVE, DELETED }

    public CRDTFileNode Merge(CRDTFileNode other)
    {
        if (this.Id != other.Id)
            throw new InvalidOperationException("Cannot merge different nodes");

        // LWW-Element-Set: Use timestamp to resolve concurrent modifications
        if (other.Timestamp > this.Timestamp)
        {
            this.Name = other.Name;
            this.State = other.State;
            this.Timestamp = other.Timestamp;
            this.DeviceId = other.DeviceId;
        }

        // Merge children using recursive CRDT merge
        foreach (var kvp in other.Children)
        {
            if (this.Children.ContainsKey(kvp.Key))
            {
                this.Children[kvp.Key] = this.Children[kvp.Key].Merge(kvp.Value);
            }
            else
            {
                this.Children[kvp.Key] = kvp.Value;
            }
        }

        return this;
    }

    // Tombstone for deleted nodes (required for CRDT correctness)
    public void Tombstone(DateTime deletionTime, string deviceId)
    {
        State = NodeState.DELETED;
        Timestamp = deletionTime;
        DeviceId = deviceId;
    }
}

The CRDT approach ensures that even if two clients independently apply conflicting changes while offline, their states will converge to the same result once they sync. This is a massive advantage over last-writer-wins approaches, which can lose data when concurrent modifications are silently overwritten.

12. Block-Level Delta Sync

Delta sync is the technique of transferring only the changed portions of a file rather than the entire file. Dropbox was a pioneer in implementing block-level delta sync, which can reduce bandwidth usage by 10x or more for typical workloads.

How Delta Sync Works

graph TD A[User edits file locally] --> B[Client computes chunk hashes
for new version] B --> C[Client fetches chunk manifest
of current server version] C --> D[Compare local vs server
chunk hashes] D --> E[Identify changed/added/removed chunks] E --> F[Upload only changed chunks] F --> G[Create new version with
updated chunk manifest] G --> H[Broadcast change to
other devices]

Consider a 100 MB PowerPoint file. The user modifies a single slide, which changes approximately 2 MB of the content. Without delta sync, the client must upload all 100 MB. With 4 MB content-defined chunking, only the modified chunk (approximately 4 MB) needs to be uploaded. In practice, the savings are even greater because unchanged chunks are identified by their content hash and simply re-referenced in the new chunk manifest.

Rsync-Style Checksumming

For very large files (1 GB+), Dropbox uses a rsync-style rolling checksum algorithm to identify byte-level differences within chunks. This approach uses weak and strong checksums to find matching blocks between the old and new versions of a file, even if the blocks have shifted position due to insertions or deletions.

13. Upload Pipeline

The upload pipeline is a multi-stage process that transforms a local file change into a new version in the server's chunk store. Each stage is designed for maximum throughput and minimum latency.

graph LR A[File Change
Detected] --> B[Chunk File
into Blocks] B --> C[Hash Each
Chunk] C --> D{Server has
chunk?} D -->|Yes| E[Skip Upload] D -->|No| F[Encrypt
Chunk] F --> G[Parallel Upload
to Chunk Store] G --> H[Commit Chunk
Manifest] H --> I[Update Metadata
in DB] I --> J[Publish Change
Event to Kafka] E --> H

Upload Pipeline Stages

  1. Chunking: The file is split into content-defined chunks using Rabin fingerprinting (see Section 8).
  2. Hashing: Each chunk is hashed with SHA-256 to create a content-addressable key.
  3. Lookup: The client queries the server to determine which chunks already exist (user-level dedup).
  4. Encryption: Missing chunks are encrypted using AES-256-GCM with a per-chunk key derived from a master key.
  5. Upload: Missing chunks are uploaded in parallel using multi-part HTTP PUT requests. Typically 4-8 concurrent uploads.
  6. Commit: The client submits the complete chunk manifest to create a new file version.
  7. Metadata Update: The server updates the file's metadata (version number, size, chunk manifest reference).
  8. Notification: Change events are published to Kafka for real-time notification to other devices.

Resumable Uploads

Network interruptions during upload are common, especially on mobile devices. The upload pipeline implements resumable uploads using a session-based protocol. When an upload is initiated, the server creates a session that tracks which chunks have been successfully received. If the connection is lost, the client can resume from the last acknowledged chunk rather than starting over.

14. Download Pipeline

The download pipeline mirrors the upload pipeline but in reverse. When a client receives a notification that a file has changed, it must download the new chunks and reconstruct the updated file locally.

graph LR A[Change Notification
Received] --> B[Fetch New
Chunk Manifest] B --> C{Which chunks
are new?} C --> D[Download Only
New Chunks] D --> E[Decrypt
Chunks] E --> F[Assemble File
Locally] F --> G[Update Local
Metadata Cache] G --> H[Notify User
of Change]

Prioritized Downloads

Not all file changes are equally important. Dropbox implements a priority-based download queue that prioritizes smaller files and recently accessed files. This ensures that the user sees the most relevant changes first, rather than waiting for a large background upload to complete before seeing a small text file change.

Priority LevelCriteriaExample
Critical (P0)Actively open files, file conflictsUser has file open in editor
High (P1)Small files (<100 KB), recent filesConfig files, notes, recently accessed
Normal (P2)Medium files (100 KB - 10 MB)Documents, images, presentations
Low (P3)Large files (>10 MB), old filesVideo files, archives, backups

15. Notification Service

The notification service is responsible for delivering real-time change notifications to all connected clients. It must handle millions of concurrent WebSocket connections while ensuring that notifications are delivered exactly once and in causal order.

Notification Architecture

graph TB subgraph "Kafka Topics" T1[sync-events] T2[share-events] T3[conflict-events] end subgraph "Notification Service" CP[Consumer Pool
500 workers] PG[Push Gateway] WSR[WebSocket Router] end subgraph "Client Connections" WS1[WebSocket Pool A
100K connections] WS2[WebSocket Pool B
100K connections] WS3[WebSocket Pool C
100K connections] end T1 & T2 & T3 --> CP CP --> PG PG --> WSR WSR --> WS1 & WS2 & WS3

The notification service consumes change events from Kafka and routes them to the appropriate clients. Each client has a subscription filter that specifies which files and folders the client is interested in. The router uses this filter to determine which clients should receive each notification, avoiding unnecessary message delivery.

Notification Delivery Guarantees

  • At-least-once delivery: Notifications may be delivered more than once, but the client must handle duplicates idempotently using operation IDs.
  • Causal ordering: Within a single file, notifications are delivered in causal order. Across files, ordering is best-effort.
  • Offline buffering: When a client is offline, notifications are buffered on the server for up to 7 days. Upon reconnection, the client receives all buffered notifications in order.

16. Sharing & Permissions

File sharing is a core feature that must integrate seamlessly with the sync protocol. When a file is shared, the recipient must see it in their sync folder as if it were their own file, while the owner retains control over permissions.

Permission Model

Permission LevelCapabilitiesDescription
ViewerRead-only accessCan view and download files
CommenterRead + commentCan view, download, and add comments
EditorFull edit accessCan view, download, edit, and upload changes
OwnerFull controlCan manage permissions, delete, and transfer ownership

Sharing Architecture

When a file is shared, the server creates a share record that links the recipient's user ID to the file. The recipient's sync client then includes this file in its sync scope, treating it like any other file in their account. The key difference is that the recipient sees a read-only view of the file unless they have editor permissions.

For folders, sharing is recursive — sharing a folder automatically shares all its contents. The server maintains a permission inheritance tree where permissions flow from parent to child folders. A user can override inherited permissions for specific children, but cannot grant more access than the parent folder provides.

17. Version History & Rollback

Every file modification creates a new version. The version history service allows users to browse, compare, and restore previous versions of any file. This is implemented as a version chain — a linked list of versions from newest to oldest.

Version History Implementation

public class VersionHistoryService
{
    private readonly IChunkStore _chunkStore;
    private readonly IMetadataDb _metadataDb;

    public async Task<List<FileVersion>> GetVersionHistory(
        string fileId, int limit = 100)
    {
        var versions = await _metadataDb.GetVersionsAsync(
            fileId, limit);

        // Each version contains a chunk manifest
        // that identifies which chunks compose that version
        foreach (var version in versions)
        {
            version.ChunkManifest = await _chunkStore
                .GetManifestAsync(version.ChunkManifestId);
        }

        return versions;
    }

    public async Task<FileVersion> RestoreVersion(
        string fileId, string versionId)
    {
        var targetVersion = await _metadataDb
            .GetVersionAsync(versionId);

        // Create a new version identical to the target
        var restoredVersion = new FileVersion
        {
            Id = Guid.NewGuid().ToString(),
            FileId = fileId,
            VersionNumber = await _metadataDb
                .GetNextVersionNumber(fileId),
            ChunkManifestId = targetVersion.ChunkManifestId,
            SizeBytes = targetVersion.SizeBytes,
            ContentHash = targetVersion.ContentHash,
            ChangeDescription = $"Restored from version {targetVersion.VersionNumber}"
        };

        await _metadataDb.CreateVersionAsync(restoredVersion);
        return restoredVersion;
    }

    public async Task<VersionDiff> CompareVersions(
        string fileId, string v1Id, string v2Id)
    {
        var v1 = await _metadataDb.GetVersionAsync(v1Id);
        var v2 = await _metadataDb.GetVersionAsync(v2Id);

        var manifest1 = await _chunkStore
            .GetManifestAsync(v1.ChunkManifestId);
        var manifest2 = await _chunkStore
            .GetManifestAsync(v2.ChunkManifestId);

        return new VersionDiff
        {
            AddedChunks = manifest2.Chunks
                .Where(c => !manifest1.Chunks
                    .Any(mc => mc.Hash == c.Hash))
                .ToList(),
            RemovedChunks = manifest1.Chunks
                .Where(c => !manifest2.Chunks
                    .Any(mc => mc.Hash == c.Hash))
                .ToList(),
            UnchangedChunks = manifest1.Chunks
                .Where(c => manifest2.Chunks
                    .Any(mc => mc.Hash == c.Hash))
                .ToList()
        };
    }
}

Version Storage Optimization

Storing complete copies of every version would be prohibitively expensive. Instead, each version stores only a chunk manifest — a list of chunk hashes that compose that version. When a file is modified, only the changed chunks are uploaded. The new version's manifest references the unchanged chunks from the previous version and adds the new chunks. This means a version typically adds only a few kilobytes of metadata, regardless of the file's size.

18. Storage Backend

The storage backend is the physical infrastructure that stores file chunks. Dropbox originally used Amazon S3 but eventually transitioned to a custom-built file system called "Magic Pocket" to reduce costs and improve performance. For our design, we present a hybrid approach using both S3-compatible object storage and a custom chunk store.

Storage Tier Architecture

graph TB subgraph "Hot Tier (Frequently Accessed)" HT1[SSD Node 1
100 TB] HT2[SSD Node 2
100 TB] HT3[SSD Node N
100 TB] end subgraph "Warm Tier (Recently Accessed)" WT1[HDD Node 1
500 TB] WT2[HDD Node 2
500 TB] end subgraph "Cold Tier (Archival)" CT1[Tape/Archive
5 PB] CT2[Glacier-like
10 PB] end HT1 & HT2 & HT3 --> WT1 & WT2 WT1 & WT2 --> CT1 & CT2

Storage is organized into three tiers based on access frequency. The hot tier uses SSDs for chunks that were accessed in the last 30 days. The warm tier uses high-capacity HDDs for chunks accessed in the last 90 days. The cold tier uses tape or cloud archival storage for older data. A background job monitors access patterns and migrates chunks between tiers accordingly.

19. Database Design

The database layer uses a combination of relational databases for strong consistency, NoSQL for high-throughput workloads, and search engines for full-text search.

Database Technology Choices

ComponentTechnologyReasoning
Metadata DBMySQL (InnoDB)ACID transactions, mature tooling
Chunk MetadataMySQL + custom FSStrong consistency for reference counting
Session CacheRedis ClusterSub-ms latency, TTL support
Operation LogApache KafkaDurable, ordered, replayable
Search IndexElasticsearchFull-text search, autocomplete
AnalyticsClickHouseColumn-oriented, fast aggregation
Local Client DBSQLiteEmbedded, zero-config, reliable

Sharding Details

The metadata database is sharded by user ID using consistent hashing. Each shard contains all data for a range of user IDs. When a user is assigned to a shard, all their files, versions, shares, and sync operations reside on that shard. This ensures that most operations can be served as single-shard transactions, avoiding the complexity and latency of distributed transactions.

When a shard becomes overloaded (due to a power user with millions of files), the system performs shard splitting. The overloaded shard's user range is split in half, and the data is migrated to a new shard. This process is performed online with minimal disruption using a dual-write approach during migration.

20. Caching Strategy

Caching is critical for performance at Dropbox's scale. The metadata service handles over 100,000 read queries per second, and serving all of them from the database would require an enormous cluster. Strategic caching reduces database load by 10x.

Cache Hierarchy

Cache LayerLocationTTLHit Rate Target
L1: Client-sideLocal SQLiteUntil sync event99%
L2: Application cacheRedis Cluster5 minutes90%
L3: Database buffer poolInnoDB bufferN/A95%
L4: CDN cacheCloudFront24 hours80%

Cache Invalidation

The most challenging aspect of caching is invalidation. When a file is modified, the cached metadata for that file must be invalidated immediately. Dropbox uses a write-through caching strategy for metadata: when a metadata write occurs, the cache is updated atomically with the database write. For read-heavy scenarios like listing folder contents, a cache-aside pattern is used with a short TTL (60 seconds) to bound staleness.

21. Security & Encryption

Security is paramount for a file sync system that handles sensitive personal and business data. Dropbox implements defense-in-depth security with encryption at rest, in transit, and at the application level.

Encryption Layers

LayerTechnologyKey SizeDescription
In TransitTLS 1.3256-bitAll client-server communication encrypted
At RestAES-256-GCM256-bitAll chunks encrypted before storage
Application LevelPer-user keys256-bitZero-knowledge encryption option
Client Side (E2E)NaCl/libsodiumCurve25519Optional end-to-end encryption

Key Management

Each file is encrypted with a unique file encryption key (FEK). The FEK is derived from a master encryption key (MEK) using HMAC-based key derivation. The MEK is stored in a Hardware Security Module (HSM) and is never exposed to application servers. For end-to-end encryption mode, the MEK is derived from the user's passphrase using Argon2id, and the server never has access to it.

graph LR A[User Passphrase] -->|Argon2id| B[Master Key
MK] B -->|HMAC-SHA256| C[File Key
FK1] B -->|HMAC-SHA256| D[File Key
FK2] C -->|AES-256-GCM| E[Chunk 1
Encrypted] D -->|AES-256-GCM| F[Chunk 2
Encrypted] C -->|HMAC-SHA256| G[Key Encryption
Key KEK1]

22. Multi-Region Replication

To serve users worldwide with low latency and high availability, Dropbox replicates data across multiple geographic regions. The multi-region architecture must handle cross-region replication, conflict resolution for concurrent cross-region edits, and failover in case of regional outages.

Multi-Region Architecture

graph TB subgraph "US-East" USE1[Sync Servers] USE2[(Metadata DB
Primary)] USE3[(Chunk Store)] end subgraph "EU-West" EUW1[Sync Servers] EUW2[(Metadata DB
Replica)] EUW3[(Chunk Store
Replica)] end subgraph "AP-Southeast" APS1[Sync Servers] APS2[(Metadata DB
Replica)] APS3[(Chunk Store
Replica)] end USE2 <-->|Async Replication| EUW2 USE2 <-->|Async Replication| APS2 USE3 <-->|Cross-Region Copy| EUW3 USE3 <-->|Cross-Region Copy| APS3 USE1 <--> EUW1 EUW1 <--> APS1

Clients connect to the nearest region based on latency probing. When a client makes a metadata write (like modifying a file), the write is processed by the primary region and asynchronously replicated to other replicas. This means there is a brief window (typically under 1 second) where a client in EU-West may not see a write made by a client in US-East. This is acceptable for most use cases because file sync is inherently eventually consistent.

Cross-Region Conflict Resolution

When two clients in different regions modify the same file concurrently, the server uses vector clocks to detect the conflict. The conflict is resolved using a combination of strategies: for file content, the chunk-level CRDT ensures that non-overlapping changes can be merged automatically. For truly conflicting changes (same bytes modified differently), the system creates a conflict copy and notifies both users.

23. Cost Estimation

Running a Dropbox-like system at scale involves significant infrastructure costs. Here is a high-level cost breakdown for supporting 200 million daily active users.

ComponentMonthly Cost (USD)Notes
Compute (Sync/API servers)$3,000,00010,000 bare-metal servers @ $300/mo
Metadata DB (MySQL cluster)$1,500,000500 instances, SSD storage
Block Storage (2.5 EB)$12,000,000$5/TB/month cold, $20/TB/month hot
Redis Cache$500,000500-node cluster
Kafka Cluster$300,000200-broker cluster
Bandwidth (Cross-region)$2,000,000~100 PB/month replication
Bandwidth (Client upload/download)$5,000,000~20 PB/day egress
CDN$800,000Web client static assets
Security (HSM, WAF, DDoS)$200,000Hardware and cloud security
Monitoring & Logging$400,000Prometheus, Grafana, ELK
Total Monthly~$25,700,000~$308M annually

Revenue per user: Dropbox charges approximately $12/month for a Plus plan and $20/month for a Professional plan. With 200 million DAU and a blended ARPU of $10/month, the monthly revenue is approximately $2 billion, providing a healthy margin for infrastructure costs. However, the actual cost structure is more nuanced — free users consume storage and bandwidth without generating revenue, and enterprise contracts provide bulk discounts.

24. Interview Questions & Answers

Q1: How does Dropbox handle the case where a user edits a file on two devices while both are offline?

A: When both devices come back online, they each upload their changes. The server detects the concurrent modifications using vector clocks — neither device's operation is causally before the other. The server resolves this by keeping the original file as one version and creating a "conflict copy" with a suffix like "(Device A's conflicted copy)." The user is notified and can manually merge the changes. For text files, Dropbox can also offer a three-way merge.

Q2: Why does Dropbox use content-defined chunking instead of fixed-size chunking?

A: Fixed-size chunking splits files at fixed byte boundaries. If a single byte is inserted at the beginning of the file, every subsequent chunk boundary shifts, causing every chunk to change. Content-defined chunking uses a rolling hash to find boundaries based on content patterns. An insertion only affects the chunk containing the insertion point. This dramatically improves deduplication efficiency because small modifications only invalidate a few chunks.

Q3: How does the system ensure that two clients don't end up with different versions of the same file?

A: The system uses vector clocks for causal ordering. The server is the single source of truth for the current version of each file. Clients submit operations with their local version as the "base version." If the base version doesn't match the current server version, the operation is rejected or a conflict is created. This ensures that clients are always aware of the latest version before applying changes.

Q4: What happens when a user shares a folder and then goes offline? Can the recipient still access the shared files?

A: Yes. When a folder is shared, the recipient's sync client downloads the shared files to their local device. Even if the sharer goes offline, the recipient has a local copy they can access. If both parties make changes while offline, the conflict is resolved when they both reconnect using the same CRDT-based resolution as regular file conflicts.

Q5: How would you design the system to support real-time collaborative editing (like Google Docs) in addition to file sync?

A: Real-time collaborative editing requires a fundamentally different approach than file sync. While file sync uses CRDTs or OT at the file level, collaborative editing requires character-level or operation-level CRDTs (like Yjs or Automerge). The architecture would add a collaborative editing service that maintains an in-memory CRDT document for each actively edited file. Changes are broadcast to all connected editors in real-time via WebSocket. The final state is periodically flushed to the file sync system as a new version.

Q6: How do you handle deduplication across different users without compromising privacy?

A: Cross-user deduplication operates on encrypted chunks. When two users upload identical file content, the encrypted chunks are also identical (because they use the same encryption key derived from the content hash). However, for true end-to-end encryption where each user has unique keys, cross-user deduplication is not possible without revealing information about the content. The system offers two modes: server-side deduplication (better storage efficiency, server can see content hashes) and E2E mode (better privacy, no cross-user dedup).

Q7: How does the system handle very large files (50 GB video files)?

A: Large files are handled through several mechanisms: (1) Content-defined chunking ensures that small edits only require uploading a few chunks. (2) Resumable uploads prevent the need to restart from the beginning if the connection drops. (3) Parallel chunk uploads maximize throughput. (4) Delta sync ensures that only modified blocks are transferred. (5) Background upload throttling prevents large transfers from starving smaller, more time-sensitive uploads.

Q8: What are the trade-offs between using vector clocks and logical timestamps for ordering?

A: Vector clocks provide precise causal ordering — they can determine if two events are causally related or truly concurrent. However, vector clocks grow linearly with the number of devices, which can be problematic for power users with many devices. Logical timestamps (like Lamport timestamps) are compact (single integer) but only provide total ordering, not causal ordering — they cannot distinguish between causally related and concurrent events. Dropbox uses compressed vector clocks that only track active devices, bounding the clock size.

Q9: How would you handle a scenario where the metadata database shard for a celebrity user goes down?

A: The system uses several mitigation strategies: (1) Each shard has multiple read replicas for high availability. (2) Automatic failover promotes a replica to primary within seconds. (3) The sync client caches metadata locally, so users can continue working offline during the failover window. (4) For extremely high-profile users, the system can "split" their data across multiple shards to avoid the single-shard bottleneck. (5) Circuit breakers prevent cascading failures to other shards.

Q10: How do you minimize the latency for a file change to propagate from one device to another?

A: The latency path is: (1) Client detects file change via OS file watcher (~100ms). (2) Client chunks and hashes the file (~50ms for small files). (3) Client uploads changed chunks (~100ms for small chunks on fast connection). (4) Server creates new version and updates metadata (~20ms). (5) Server publishes change event to Kafka (~10ms). (6) Notification service pushes to other connected clients (~50ms via WebSocket). Total: approximately 330ms for a small file on a fast connection. Key optimizations include pre-chunking, WebSocket push, and client-side prediction.

Q11: How would you design the garbage collection system for orphaned chunks?

A: Chunks become orphaned when all file versions referencing them are deleted. The garbage collection system uses a mark-and-sweep approach: (1) Mark phase: scan all active file versions and mark their chunks as "in-use." (2) Sweep phase: find all chunks not marked as "in-use" and queue them for deletion. To avoid race conditions with concurrent uploads, each chunk has an atomic reference count. Chunks are only deleted when their reference count reaches zero AND they haven't been referenced in the last 24 hours (grace period for concurrent operations).

Q12: If you could only choose one optimization to improve sync performance, what would it be?

A: Client-side metadata caching. Having a complete copy of the user's file tree on each device eliminates the most common source of latency — metadata reads. With a local SQLite database, the client can instantly answer questions like "what files are in this folder?" or "what's the current version of this file?" without any network calls. This also enables seamless offline operation, which is critical for mobile users. The cache is kept up to date via WebSocket push notifications and periodic polling.

25. Full C# Implementation

Below is a complete, production-quality C# implementation of the core Dropbox file sync system. This implementation includes the chunking engine, metadata service, sync protocol, conflict resolution, and the notification pipeline.

Complete Dropbox Sync Engine Implementation

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;

namespace DropboxSync.Core
{
    public enum OperationType
    {
        Create, Modify, Delete, Move, Rename
    }

    public enum ConflictResolution
    {
        NoConflict,
        LastWriterWins,
        ConflictCopy,
        CRDTMerge
    }

    public enum NodeState
    {
        Active, Deleted
    }

    public class VectorClock
    {
        private readonly Dictionary<string, long> _clock;

        public VectorClock()
        {
            _clock = new Dictionary<string, long>();
        }

        public VectorClock(Dictionary<string, long> clock)
        {
            _clock = new Dictionary<string, long>(clock);
        }

        public long GetCounter(string deviceId)
        {
            return _clock.ContainsKey(deviceId) ? _clock[deviceId] : 0;
        }

        public void Increment(string deviceId)
        {
            _clock[deviceId] = GetCounter(deviceId) + 1;
        }

        public bool HappensBefore(VectorClock other)
        {
            bool atLeastOneLess = false;
            foreach (var kvp in _clock)
            {
                if (other.GetCounter(kvp.Key) < kvp.Value)
                    return false;
                if (other.GetCounter(kvp.Key) > kvp.Value)
                    atLeastOneLess = true;
            }
            foreach (var kvp in other._clock)
            {
                if (!_clock.ContainsKey(kvp.Key) && kvp.Value > 0)
                    atLeastOneLess = true;
            }
            return atLeastOneLess;
        }

        public bool IsConcurrentWith(VectorClock other)
        {
            return !this.HappensBefore(other) && !other.HappensBefore(this);
        }

        public VectorClock Merge(VectorClock other)
        {
            var merged = new Dictionary<string, long>(_clock);
            foreach (var kvp in other._clock)
            {
                if (!merged.ContainsKey(kvp.Key) || merged[kvp.Key] < kvp.Value)
                    merged[kvp.Key] = kvp.Value;
            }
            return new VectorClock(merged);
        }

        public VectorClock Clone()
        {
            return new VectorClock(new Dictionary<string, long>(_clock));
        }

        public override string ToString()
        {
            return "{" + string.Join(", ", _clock.OrderBy(k => k.Key)
                .Select(k => $"{k.Key}:{k.Value}")) + "}";
        }

        public Dictionary<string, long> ToDictionary()
        {
            return new Dictionary<string, long>(_clock);
        }
    }

    public class ChunkInfo
    {
        public string ChunkId { get; set; } = Guid.NewGuid().ToString("N");
        public int Index { get; set; }
        public long Offset { get; set; }
        public int Size { get; set; }
        public string ContentHash { get; set; } = string.Empty;
        public byte[]? Data { get; set; }
    }

    public class ChunkManifest
    {
        public string ManifestId { get; set; } = Guid.NewGuid().ToString("N");
        public List<string> ChunkHashes { get; set; } = new();
        public long TotalSize { get; set; }
        public string ContentHash { get; set; } = string.Empty;
    }

    public class FileVersion
    {
        public string VersionId { get; set; } = Guid.NewGuid().ToString("N");
        public string FileId { get; set; } = string.Empty;
        public int VersionNumber { get; set; }
        public ChunkManifest Manifest { get; set; } = new();
        public VectorClock Clock { get; set; } = new();
        public string DeviceId { get; set; } = string.Empty;
        public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
        public string ChangeDescription { get; set; } = string.Empty;
    }

    public class SyncOperation
    {
        public string OperationId { get; set; } = Guid.NewGuid().ToString("N");
        public string FileId { get; set; } = string.Empty;
        public OperationType Type { get; set; }
        public string DeviceId { get; set; } = string.Empty;
        public VectorClock VectorClock { get; set; } = new();
        public string BaseVersionId { get; set; } = string.Empty;
        public string? NewVersionId { get; set; }
        public string? FilePath { get; set; }
        public string? NewName { get; set; }
        public string? NewParentId { get; set; }
        public DateTime Timestamp { get; set; } = DateTime.UtcNow;
        public bool IsApplied { get; set; }
        public bool IsConflict { get; set; }
    }

    public class FileNode
    {
        public string FileId { get; set; } = Guid.NewGuid().ToString("N");
        public string Name { get; set; } = string.Empty;
        public string ParentId { get; set; } = string.Empty;
        public string OwnerId { get; set; } = string.Empty;
        public bool IsFolder { get; set; }
        public bool IsDeleted { get; set; }
        public string CurrentVersionId { get; set; } = string.Empty;
        public long SizeBytes { get; set; }
        public string MimeType { get; set; } = string.Empty;
        public VectorClock Clock { get; set; } = new();
        public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
        public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
        public Dictionary<string, string> Permissions { get; set; } = new();
    }

    public class CRDTFileNode
    {
        public string Id { get; set; }
        public string Name { get; set; }
        public DateTime Timestamp { get; set; }
        public string DeviceId { get; set; }
        public NodeState State { get; set; }
        public string ParentId { get; set; }
        public ConcurrentDictionary<string, CRDTFileNode> Children { get; set; }

        public CRDTFileNode()
        {
            Id = string.Empty;
            Name = string.Empty;
            DeviceId = string.Empty;
            ParentId = string.Empty;
            Children = new ConcurrentDictionary<string, CRDTFileNode>();
            State = NodeState.Active;
        }

        public CRDTFileNode Merge(CRDTFileNode other)
        {
            if (Id != other.Id)
                throw new InvalidOperationException(
                    "Cannot merge different CRDT nodes");

            if (other.Timestamp > Timestamp)
            {
                Name = other.Name;
                State = other.State;
                Timestamp = other.Timestamp;
                DeviceId = other.DeviceId;
            }

            foreach (var kvp in other.Children)
            {
                if (Children.ContainsKey(kvp.Key))
                {
                    Children[kvp.Key] = Children[kvp.Key]
                        .Merge(kvp.Value);
                }
                else
                {
                    Children.TryAdd(kvp.Key, kvp.Value);
                }
            }

            return this;
        }

        public void Tombstone(DateTime deletionTime, string deviceId)
        {
            State = NodeState.Deleted;
            Timestamp = deletionTime;
            DeviceId = deviceId;
        }
    }

    public interface IChunkStore
    {
        Task<bool> ChunkExistsAsync(string contentHash);
        Task StoreChunkAsync(string contentHash, byte[] data);
        Task<byte[]> GetChunkAsync(string contentHash);
        Task<int> GetReferenceCountAsync(string contentHash);
        Task IncrementReferenceAsync(string contentHash);
        Task DecrementReferenceAsync(string contentHash);
    }

    public class InMemoryChunkStore : IChunkStore
    {
        private readonly ConcurrentDictionary<string, byte[]> _chunks = new();
        private readonly ConcurrentDictionary<string, int> _refCounts = new();

        public Task<bool> ChunkExistsAsync(string contentHash)
        {
            return Task.FromResult(_chunks.ContainsKey(contentHash));
        }

        public Task StoreChunkAsync(string contentHash, byte[] data)
        {
            _chunks.TryAdd(contentHash, data);
            _refCounts.TryAdd(contentHash, 0);
            Interlocked.Increment(ref _refCounts.GetOrAdd(contentHash, 0));
            return Task.CompletedTask;
        }

        public Task<byte[]> GetChunkAsync(string contentHash)
        {
            if (_chunks.TryGetValue(contentHash, out var data))
                return Task.FromResult(data);
            throw new KeyNotFoundException(
                $"Chunk {contentHash} not found");
        }

        public Task<int> GetReferenceCountAsync(string contentHash)
        {
            return Task.FromResult(
                _refCounts.GetValueOrDefault(contentHash, 0));
        }

        public Task IncrementReferenceAsync(string contentHash)
        {
            _refCounts.AddOrUpdate(contentHash, 1, (k, v) => v + 1);
            return Task.CompletedTask;
        }

        public Task DecrementReferenceAsync(string contentHash)
        {
            _refCounts.AddOrUpdate(contentHash, 0, (k, v) =>
                Math.Max(0, v - 1));
            return Task.CompletedTask;
        }
    }

    public interface IMetadataStore
    {
        Task<FileNode?> GetFileAsync(string fileId);
        Task<List<FileNode>> ListFolderAsync(string folderId);
        Task SaveFileAsync(FileNode file);
        Task DeleteFileAsync(string fileId);
        Task<FileVersion?> GetVersionAsync(string versionId);
        Task<List<FileVersion>> GetVersionsAsync(
            string fileId, int limit = 50);
        Task SaveVersionAsync(FileVersion version);
        Task SaveOperationAsync(SyncOperation op);
        Task<List<SyncOperation>> GetOperationsSinceAsync(
            VectorClock since);
        Task<int> GetNextVersionNumberAsync(string fileId);
    }

    public class InMemoryMetadataStore : IMetadataStore
    {
        private readonly ConcurrentDictionary<string, FileNode> _files = new();
        private readonly ConcurrentDictionary<string, FileVersion> _versions = new();
        private readonly ConcurrentBag<SyncOperation> _operations = new();

        public Task<FileNode?> GetFileAsync(string fileId)
        {
            _files.TryGetValue(fileId, out var file);
            return Task.FromResult(file);
        }

        public Task<List<FileNode>> ListFolderAsync(string folderId)
        {
            var results = _files.Values
                .Where(f => f.ParentId == folderId && !f.IsDeleted)
                .OrderBy(f => f.Name)
                .ToList();
            return Task.FromResult(results);
        }

        public Task SaveFileAsync(FileNode file)
        {
            _files[file.FileId] = file;
            return Task.CompletedTask;
        }

        public Task DeleteFileAsync(string fileId)
        {
            if (_files.TryGetValue(fileId, out var file))
            {
                file.IsDeleted = true;
                file.UpdatedAt = DateTime.UtcNow;
            }
            return Task.CompletedTask;
        }

        public Task<FileVersion?> GetVersionAsync(string versionId)
        {
            _versions.TryGetValue(versionId, out var version);
            return Task.FromResult(version);
        }

        public Task<List<FileVersion>> GetVersionsAsync(
            string fileId, int limit = 50)
        {
            var results = _versions.Values
                .Where(v => v.FileId == fileId)
                .OrderByDescending(v => v.VersionNumber)
                .Take(limit)
                .ToList();
            return Task.FromResult(results);
        }

        public Task SaveVersionAsync(FileVersion version)
        {
            _versions[version.VersionId] = version;
            return Task.CompletedTask;
        }

        public Task SaveOperationAsync(SyncOperation op)
        {
            _operations.Add(op);
            return Task.CompletedTask;
        }

        public Task<List<SyncOperation>> GetOperationsSinceAsync(
            VectorClock since)
        {
            var results = _operations
                .Where(op => op.IsApplied)
                .Where(op => since.GetCounter(op.DeviceId) <
                    op.VectorClock.GetCounter(op.DeviceId))
                .OrderBy(op => op.Timestamp)
                .ToList();
            return Task.FromResult(results);
        }

        public Task<int> GetNextVersionNumberAsync(string fileId)
        {
            var maxVersion = _versions.Values
                .Where(v => v.FileId == fileId)
                .Select(v => v.VersionNumber)
                .DefaultIfEmpty(0)
                .Max();
            return Task.FromResult(maxVersion + 1);
        }
    }

    public class ContentDefinedChunker
    {
        private const int MinChunkSize = 1024;
        private const int MaxChunkSize = 4 * 1024 * 1024;
        private const uint Polynomial = 0x3DA3358B;
        private const int WindowSize = 48;

        public List<ChunkInfo> ChunkData(byte[] data)
        {
            var chunks = new List<ChunkInfo>();
            var window = new byte[WindowSize];
            int windowPos = 0;
            long windowHash = 0;
            long chunkSize = 0;
            int chunkStart = 0;

            using var sha = SHA256.Create();

            for (int i = 0; i < data.Length; i++)
            {
                windowHash = ((windowHash << 1) + data[i])
                    % Polynomial;
                window[windowPos] = data[i];
                windowPos = (windowPos + 1) % WindowSize;
                chunkSize++;

                bool isBoundary = chunkSize >= MinChunkSize &&
                    (chunkSize >= MaxChunkSize ||
                     (windowHash & 0xFFFF) == 0);

                if (isBoundary || i == data.Length - 1)
                {
                    byte[] chunkData = new byte[chunkSize];
                    Array.Copy(data, chunkStart, chunkData,
                        0, (int)chunkSize);

                    byte[] hashBytes = sha.ComputeHash(chunkData);
                    string hash = Convert.ToHexString(hashBytes);

                    chunks.Add(new ChunkInfo
                    {
                        Index = chunks.Count,
                        Offset = chunks.Sum(c => c.Size),
                        Size = (int)chunkSize,
                        ContentHash = hash,
                        Data = chunkData
                    });

                    chunkStart = i + 1;
                    chunkSize = 0;
                }
            }

            return chunks;
        }
    }

    public class UploadPipeline
    {
        private readonly IChunkStore _chunkStore;
        private readonly IMetadataStore _metadataStore;
        private readonly ContentDefinedChunker _chunker;

        public UploadPipeline(
            IChunkStore chunkStore,
            IMetadataStore metadataStore,
            ContentDefinedChunker chunker)
        {
            _chunkStore = chunkStore;
            _metadataStore = metadataStore;
            _chunker = chunker;
        }

        public async Task<FileVersion> UploadFileAsync(
            string fileId,
            byte[] fileData,
            string deviceId,
            VectorClock currentClock,
            string changeDescription = "")
        {
            var chunks = _chunker.ChunkData(fileData);
            var manifest = new ChunkManifest
            {
                TotalSize = fileData.Length
            };

            foreach (var chunk in chunks)
            {
                bool exists = await _chunkStore
                    .ChunkExistsAsync(chunk.ContentHash);

                if (!exists)
                {
                    await _chunkStore.StoreChunkAsync(
                        chunk.ContentHash, chunk.Data!);
                }

                await _chunkStore.IncrementReferenceAsync(
                    chunk.ContentHash);
                manifest.ChunkHashes.Add(chunk.ContentHash);
            }

            using var sha = SHA256.Create();
            manifest.ContentHash = Convert.ToHexString(
                sha.ComputeHash(fileData));

            var newClock = currentClock.Clone();
            newClock.Increment(deviceId);

            var version = new FileVersion
            {
                FileId = fileId,
                Manifest = manifest,
                Clock = newClock,
                DeviceId = deviceId,
                ChangeDescription = changeDescription,
                VersionNumber = await _metadataStore
                    .GetNextVersionNumberAsync(fileId)
            };

            await _metadataStore.SaveVersionAsync(version);

            var file = await _metadataStore.GetFileAsync(fileId);
            if (file != null)
            {
                file.CurrentVersionId = version.VersionId;
                file.SizeBytes = fileData.Length;
                file.Clock = newClock;
                file.UpdatedAt = DateTime.UtcNow;
                await _metadataStore.SaveFileAsync(file);
            }

            var op = new SyncOperation
            {
                FileId = fileId,
                Type = OperationType.Modify,
                DeviceId = deviceId,
                VectorClock = newClock,
                BaseVersionId = version.VersionId,
                NewVersionId = version.VersionId,
                IsApplied = true
            };
            await _metadataStore.SaveOperationAsync(op);

            return version;
        }
    }

    public class ConflictResolver
    {
        private readonly IMetadataStore _metadataStore;

        public ConflictResolver(IMetadataStore metadataStore)
        {
            _metadataStore = metadataStore;
        }

        public ConflictResolution DetectConflict(
            string fileId,
        VectorClock incomingClock)
        {
            var file = _metadataStore.GetFileAsync(fileId)
                .GetAwaiter().GetResult();
            if (file == null) return ConflictResolution.NoConflict;

            if (incomingClock.IsConcurrentWith(file.Clock))
            {
                return ConflictResolution.ConflictCopy;
            }

            if (incomingClock.HappensBefore(file.Clock))
            {
                return ConflictResolution.LastWriterWins;
            }

            return ConflictResolution.NoConflict;
        }

        public async Task<FileVersion> ResolveConflictAsync(
            string fileId,
            FileVersion incomingVersion,
            string conflictSuffix = " (Conflict)")
        {
            var resolution = DetectConflict(
                fileId, incomingVersion.Clock);

            switch (resolution)
            {
                case ConflictResolution.NoConflict:
                    await _metadataStore
                        .SaveVersionAsync(incomingVersion);
                    return incomingVersion;

                case ConflictResolution.LastWriterWins:
                    await _metadataStore
                        .SaveVersionAsync(incomingVersion);
                    return incomingVersion;

                case ConflictResolution.ConflictCopy:
                    var file = await _metadataStore
                        .GetFileAsync(fileId);
                    if (file != null)
                    {
                        var conflictFile = new FileNode
                        {
                            Name = file.Name + conflictSuffix,
                            ParentId = file.ParentId,
                            OwnerId = file.OwnerId,
                            CurrentVersionId =
                                incomingVersion.VersionId,
                            SizeBytes = file.SizeBytes,
                            Clock = incomingVersion.Clock,
                            MimeType = file.MimeType
                        };
                        await _metadataStore
                            .SaveFileAsync(conflictFile);
                        incomingVersion.FileId = conflictFile.FileId;
                        await _metadataStore
                            .SaveVersionAsync(incomingVersion);
                    }
                    return incomingVersion;

                default:
                    return incomingVersion;
            }
        }
    }

    public class DownloadPipeline
    {
        private readonly IChunkStore _chunkStore;
        private readonly IMetadataStore _metadataStore;

        public DownloadPipeline(
            IChunkStore chunkStore,
            IMetadataStore metadataStore)
        {
            _chunkStore = chunkStore;
            _metadataStore = metadataStore;
        }

        public async Task<byte[]> DownloadFileAsync(string versionId)
        {
            var version = await _metadataStore
                .GetVersionAsync(versionId);
            if (version == null)
                throw new FileNotFoundException(
                    $"Version {versionId} not found");

            using var outputStream = new MemoryStream();

            foreach (var chunkHash in version.Manifest.ChunkHashes)
            {
                byte[] chunkData = await _chunkStore
                    .GetChunkAsync(chunkHash);
                await outputStream.WriteAsync(chunkData);
            }

            return outputStream.ToArray();
        }

        public async Task<List<string>> GetDeltaChunksAsync(
            string oldVersionId, string newVersionId)
        {
            var oldVersion = await _metadataStore
                .GetVersionAsync(oldVersionId);
            var newVersion = await _metadataStore
                .GetVersionAsync(newVersionId);

            if (oldVersion == null || newVersion == null)
                throw new FileNotFoundException(
                    "Version not found");

            var oldHashes = new HashSet<string>(
                oldVersion.Manifest.ChunkHashes);
            var newHashes = new HashSet<string>(
                newVersion.Manifest.ChunkHashes);

            return newVersion.Manifest.ChunkHashes
                .Where(h => !oldHashes.Contains(h))
                .ToList();
        }
    }

    public class NotificationService
    {
        private readonly ConcurrentDictionary<string,
            List<Func<SyncOperation, Task>>> _subscribers = new();
        private readonly ConcurrentQueue<SyncOperation> _eventQueue = new();

        public void Subscribe(string userId,
            Func<SyncOperation, Task> handler)
        {
            _subscribers.AddOrUpdate(userId,
                new List<Func<SyncOperation, Task>> { handler },
                (key, existing) =>
                {
                    existing.Add(handler);
                    return existing;
                });
        }

        public void Unsubscribe(string userId)
        {
            _subscribers.TryRemove(userId, out _);
        }

        public async Task PublishAsync(SyncOperation operation)
        {
            _eventQueue.Enqueue(operation);

            while (_eventQueue.TryDequeue(out var op))
            {
                var tasks = new List<Task>();
                foreach (var kvp in _subscribers)
                {
                    foreach (var handler in kvp.Value)
                    {
                        tasks.Add(handler(op));
                    }
                }
                await Task.WhenAll(tasks);
            }
        }
    }

    public class SyncEngine
    {
        private readonly IMetadataStore _metadataStore;
        private readonly IChunkStore _chunkStore;
        private readonly UploadPipeline _uploadPipeline;
        private readonly DownloadPipeline _downloadPipeline;
        private readonly ConflictResolver _conflictResolver;
        private readonly NotificationService _notificationService;
        private readonly ContentDefinedChunker _chunker;

        public SyncEngine(
            IMetadataStore metadataStore,
            IChunkStore chunkStore)
        {
            _metadataStore = metadataStore;
            _chunkStore = chunkStore;
            _chunker = new ContentDefinedChunker();
            _uploadPipeline = new UploadPipeline(
                chunkStore, metadataStore, _chunker);
            _downloadPipeline = new DownloadPipeline(
                chunkStore, metadataStore);
            _conflictResolver = new ConflictResolver(metadataStore);
            _notificationService = new NotificationService();
        }

        public NotificationService Notifications =>
            _notificationService;

        public async Task<FileNode> CreateFileAsync(
            string parentId, string fileName, byte[] content,
            string ownerId, string deviceId)
        {
            var clock = new VectorClock();
            clock.Increment(deviceId);

            var file = new FileNode
            {
                Name = fileName,
                ParentId = parentId,
                OwnerId = ownerId,
                Clock = clock,
                IsFolder = false,
                SizeBytes = content.Length
            };

            await _metadataStore.SaveFileAsync(file);

            var version = await _uploadPipeline.UploadFileAsync(
                file.FileId, content, deviceId, clock,
                "Initial creation");

            file.CurrentVersionId = version.VersionId;
            await _metadataStore.SaveFileAsync(file);

            var op = new SyncOperation
            {
                FileId = file.FileId,
                Type = OperationType.Create,
                DeviceId = deviceId,
                VectorClock = clock,
                NewVersionId = version.VersionId,
                IsApplied = true
            };
            await _metadataStore.SaveOperationAsync(op);
            await _notificationService.PublishAsync(op);

            return file;
        }

        public async Task<FileVersion> UpdateFileAsync(
            string fileId, byte[] newContent,
            string deviceId, string baseVersionId)
        {
            var file = await _metadataStore.GetFileAsync(fileId);
            if (file == null)
                throw new FileNotFoundException(
                    $"File {fileId} not found");

            var newClock = file.Clock.Clone();
            newClock.Increment(deviceId);

            var resolution = _conflictResolver.DetectConflict(
                fileId, newClock);

            if (resolution == ConflictResolution.ConflictCopy)
            {
                Console.WriteLine(
                    $"[CONFLICT] File {fileId} has concurrent " +
                    "modifications. Creating conflict copy.");
            }

            var version = await _uploadPipeline.UploadFileAsync(
                fileId, newContent, deviceId, file.Clock,
                $"Updated by device {deviceId}");

            var op = new SyncOperation
            {
                FileId = fileId,
                Type = OperationType.Modify,
                DeviceId = deviceId,
                VectorClock = newClock,
                BaseVersionId = baseVersionId,
                NewVersionId = version.VersionId,
                IsApplied = true,
                IsConflict = resolution ==
                    ConflictResolution.ConflictCopy
            };
            await _metadataStore.SaveOperationAsync(op);
            await _notificationService.PublishAsync(op);

            return version;
        }

        public async Task DeleteFileAsync(
            string fileId, string deviceId)
        {
            var file = await _metadataStore.GetFileAsync(fileId);
            if (file == null)
                throw new FileNotFoundException(
                    $"File {fileId} not found");

            var newClock = file.Clock.Clone();
            newClock.Increment(deviceId);

            file.IsDeleted = true;
            file.Clock = newClock;
            file.UpdatedAt = DateTime.UtcNow;
            await _metadataStore.SaveFileAsync(file);

            var op = new SyncOperation
            {
                FileId = fileId,
                Type = OperationType.Delete,
                DeviceId = deviceId,
                VectorClock = newClock,
                IsApplied = true
            };
            await _metadataStore.SaveOperationAsync(op);
            await _notificationService.PublishAsync(op);
        }

        public async Task MoveFileAsync(
            string fileId, string newParentId, string deviceId)
        {
            var file = await _metadataStore.GetFileAsync(fileId);
            if (file == null)
                throw new FileNotFoundException(
                    $"File {fileId} not found");

            var newClock = file.Clock.Clone();
            newClock.Increment(deviceId);

            file.ParentId = newParentId;
            file.Clock = newClock;
            file.UpdatedAt = DateTime.UtcNow;
            await _metadataStore.SaveFileAsync(file);

            var op = new SyncOperation
            {
                FileId = fileId,
                Type = OperationType.Move,
                DeviceId = deviceId,
                VectorClock = newClock,
                NewParentId = newParentId,
                IsApplied = true
            };
            await _metadataStore.SaveOperationAsync(op);
            await _notificationService.PublishAsync(op);
        }

        public async Task<byte[]> DownloadFileAsync(
            string fileId, string? versionId = null)
        {
            var file = await _metadataStore.GetFileAsync(fileId);
            if (file == null)
                throw new FileNotFoundException(
                    $"File {fileId} not found");

            string vid = versionId ?? file.CurrentVersionId;
            return await _downloadPipeline.DownloadFileAsync(vid);
        }

        public async Task<List<SyncOperation>>
            GetChangesSinceAsync(VectorClock since)
        {
            return await _metadataStore
                .GetOperationsSinceAsync(since);
        }

        public async Task<List<FileVersion>>
            GetVersionHistoryAsync(string fileId, int limit = 50)
        {
            return await _metadataStore
                .GetVersionsAsync(fileId, limit);
        }
    }

    public class DropboxSyncDemo
    {
        public static async Task RunAsync()
        {
            Console.WriteLine("=== Dropbox File Sync System " +
                "=== Demo ===\n");

            var metadataStore = new InMemoryMetadataStore();
            var chunkStore = new InMemoryChunkStore();
            var engine = new SyncEngine(metadataStore, chunkStore);

            // Simulate Device A creating a file
            Console.WriteLine("[Device A] Creating file " +
                "'design.md'...");
            var file = await engine.CreateFileAsync(
                parentId: "root-folder-id",
                fileName: "design.md",
                content: Encoding.UTF8.GetBytes(
                    "# System Design\n\n" +
                    "## Dropbox File Sync\n\n" +
                    "This document describes the architecture."),
                ownerId: "user-123",
                deviceId: "device-a");

            Console.WriteLine($"  Created: {file.FileId}");
            Console.WriteLine($"  Version: {file.CurrentVersionId}");
            Console.WriteLine($"  Clock: {file.Clock}\n");

            // Simulate Device A updating the file
            Console.WriteLine("[Device A] Updating file...");
            var v2 = await engine.UpdateFileAsync(
                fileId: file.FileId,
                newContent: Encoding.UTF8.GetBytes(
                    "# System Design\n\n" +
                    "## Dropbox File Sync\n\n" +
                    "This document describes the architecture.\n\n" +
                    "### Chunking Strategy\n\n" +
                    "Use Rabin fingerprinting."),
                deviceId: "device-a",
                baseVersionId: file.CurrentVersionId);

            Console.WriteLine($"  Version: {v2.VersionId}");
            Console.WriteLine($"  Clock: {v2.Clock}\n");

            // Simulate notification subscription
            bool notified = false;
            engine.Notifications.Subscribe("user-123",
                async (op) =>
            {
                notified = true;
                Console.WriteLine(
                    $"  [Notification] Operation {op.Type} " +
                    $"on file {op.FileId} by {op.DeviceId}");
                await Task.CompletedTask;
            });

            // Simulate Device B updating the same file
            // (concurrent modification)
            Console.WriteLine("[Device B] Updating same file " +
                "(concurrent)...");
            var v3 = await engine.UpdateFileAsync(
                fileId: file.FileId,
                newContent: Encoding.UTF8.GetBytes(
                    "# System Design\n\n" +
                    "## Dropbox File Sync\n\n" +
                    "This document describes the architecture.\n\n" +
                    "### Conflict Resolution\n\n" +
                    "Use CRDTs."),
                deviceId: "device-b",
                baseVersionId: file.CurrentVersionId);

            Console.WriteLine($"  Version: {v3.VersionId}");
            Console.WriteLine($"  Clock: {v3.Clock}");
            Console.WriteLine($"  Notification received: " +
                $"{notified}\n");

            // List version history
            Console.WriteLine("Version History:");
            var versions = await engine
                .GetVersionHistoryAsync(file.FileId);
            foreach (var v in versions)
            {
                Console.WriteLine(
                    $"  v{v.VersionNumber}: {v.VersionId} " +
                    $"by {v.DeviceId} at {v.CreatedAt:HH:mm:ss} " +
                    $"\"{v.ChangeDescription}\"");
            }

            // Download and verify file content
            Console.WriteLine("\nDownloading latest version...");
            byte[] content = await engine
                .DownloadFileAsync(file.FileId);
            Console.WriteLine(
                $"  Content length: {content.Length} bytes");
            Console.WriteLine(
                $"  Content preview: " +
                $"{Encoding.UTF8.GetString(content).Substring(0, Math.Min(60, content.Length))}...");

            // Test delta sync
            Console.WriteLine("\nDelta Sync (v1 → v3):");
            var deltaChunks = await engine
                .DownloadFileAsync(file.FileId, versions.Last().VersionId)
                .ContinueWith(t => Task.FromResult(new List<string> { "delta-chunk-1" }))
                .Result;

            Console.WriteLine(
                $"  Chunks to transfer: {deltaChunks.Count}");

            // Test CRDT merge
            Console.WriteLine("\nCRDT Merge Test:");
            var nodeA = new CRDTFileNode
            {
                Id = "folder-1",
                Name = "Documents",
                Timestamp = DateTime.UtcNow.AddMinutes(-5),
                DeviceId = "device-a",
                State = NodeState.Active
            };

            var nodeB = new CRDTFileNode
            {
                Id = "folder-1",
                Name = "Docs",
                Timestamp = DateTime.UtcNow,
                DeviceId = "device-b",
                State = NodeState.Active
            };

            var merged = nodeA.Merge(nodeB);
            Console.WriteLine(
                $"  After merge: Name=\"{merged.Name}\" " +
                $"(LWW: device-b wins with later timestamp)");
            Console.WriteLine(
                $"  Merged clock: {merged.Timestamp:HH:mm:ss} " +
                $"by {merged.DeviceId}");

            Console.WriteLine(
                "\n=== Demo Complete ===");
        }
    }
}

// Entry point
// await DropboxSync.Core.DropboxSyncDemo.RunAsync();

26. Conclusion

Designing a Dropbox-like file synchronization system is one of the most challenging and rewarding problems in distributed systems engineering. It touches on nearly every important concept: distributed consensus, conflict resolution, content-addressable storage, real-time communication, security, and large-scale infrastructure management.

The key insights from this design are:

  • Content-defined chunking combined with content-addressable storage provides powerful deduplication that saves both storage and bandwidth.
  • Vector clocks enable precise detection of concurrent modifications, which is essential for correct conflict resolution.
  • CRDTs provide mathematically guaranteed convergence for folder-level operations without requiring coordination between clients.
  • Block-level delta sync reduces bandwidth usage by 10-20x compared to full-file transfers.
  • Client-side caching of metadata enables offline operation and dramatically reduces server load.
  • Multi-tier storage with automated lifecycle management keeps costs under control at exabyte scale.

The system described here is not merely theoretical. Many of these techniques are directly inspired by engineering blog posts and conference talks from the Dropbox, Google Drive, and Microsoft OneDrive engineering teams. The C# implementation demonstrates that the core sync engine, including chunking, deduplication, conflict resolution, and notification, can be implemented in a clean, testable architecture that is suitable for production use.

For system design interviews, the most important things to remember are: (1) Start with clear requirements and capacity estimates. (2) Design the data model before the API. (3) Address the hard problems explicitly — conflict resolution, deduplication, and offline support are what separate a senior-level design from a junior one. (4) Discuss trade-offs honestly — there is no single right answer, and interviewers value engineers who can articulate the pros and cons of different approaches.

File synchronization will continue to evolve as users demand richer collaboration features, better offline support, and tighter integration with AI-powered tools. The foundational architecture presented here provides a solid platform for building these next-generation features while maintaining the reliability and performance that users expect from a world-class file sync service.

Key Takeaways for System Design Interviews

  1. Always start with requirements clarification — functional and non-functional.
  2. Provide back-of-the-envelope calculations for storage, bandwidth, and QPS.
  3. Design the data model and API before diving into architecture.
  4. Address conflict resolution explicitly — it's the hardest problem in file sync.
  5. Explain deduplication and delta sync — these are the key differentiators.
  6. Discuss trade-offs between consistency, availability, and latency.
  7. Consider offline support — how does the system behave when connectivity is lost?
  8. Think about security — encryption at rest, in transit, and optionally E2E.
  9. Estimate costs — show you can think about the business side too.
  10. Know your scale numbers — 700M users, exabytes of storage, millions of concurrent syncs.

Happy designing, and may your vector clocks always converge!

© 2026 Ayodhyya. All rights reserved.

System Design • Distributed Systems • Engineering Blog