•CASE STUDY

Duplicate File Detection at Scale

6 min read·1,200 words·Intermediate

Asked at

2 candidate reports between Apr 2026 and May 2026

How to use this case study

SDE-2 / Mid

  • Explain the cheap-first pipeline
  • Grouping by size
  • Then a partial hash
  • Then a full hash
  • Why it saves I/O

SDE-3 / Senior

  • Go deeper on hash choice and collisions
  • Parallelizing across machines
  • Files that change during the scan
  • Incremental re-scans

Staff / Principal

  • Discuss measuring the real bottleneck (disk, network, CPU)
  • Petabyte-scale runs with MapReduce-style shuffles
  • Safe actions on duplicates (hard links, dedup, deletion)

Problem RestatementProblem

Find all files with identical content in a huge file system or storage fleet: billions of files and petabytes of data. Return groups of duplicates so they can be removed or deduplicated. File names don't matter. Two files are duplicates only if their bytes are the same.

Anthropic asked this with a focus on measuring and fixing bottlenecks as it scales, and OpenAI asked about efficient I/O and correctness.

RequirementsRequirements

1.1 Functional

  • Input: one or more root folders (or a list of storage buckets).
  • Output: groups of paths with identical content.
  • Optional: re-run incrementally, only checking changed files.

1.2 Non-Functional

  • Correct: never report two different files as duplicates.
  • Efficient: avoid reading every byte of every file when we don't need to.
  • Scalable: work across many machines.
  • Robust to files that change or disappear during the scan.

1.3 Scale Estimates

  • 1 billion files, 2 PB total.
  • Reading everything at 500 MB/s per disk would take years on one disk. We must avoid reading data where possible and parallelize the rest.

The Cheap-First Pipeline (single machine first)

The key insight: most files can't be duplicates of each other, and we can prove that cheaply.

  1. Walk the tree and collect (path, size, inode, mtime). Metadata only, no content reads.
  2. Group by size. A file with a unique size has no duplicate, so drop it. This alone removes most files.
  3. Skip hard links: the same inode means the same file, not a copy.
  4. For each size group, compute a partial hash of the first 4 KB (plus maybe the last 4 KB). Split the groups by that hash, and drop files that become unique.
  5. For the remaining candidates, compute a full hash (SHA-256, or the faster BLAKE3). Files with the same full hash are duplicates.
  6. (Optional, for absolute certainty) Compare bytes of files with equal hashes. With SHA-256, a random collision is practically impossible, so most systems skip this.

Architecture diagram
%%{init: {"look":"handDrawn","handDrawnSeed":7,"theme":"base","fontFamily":"Virgil, \"Segoe Print\", \"Comic Sans MS\", cursive","themeVariables":{"fontFamily":"Virgil, \"Segoe Print\", \"Comic Sans MS\", cursive","fontSize":"16px","primaryColor":"#fff4e6","primaryBorderColor":"#1e1e1e","primaryTextColor":"#1e1e1e","secondaryColor":"#e7f5ff","tertiaryColor":"#ebfbee","lineColor":"#1e1e1e","textColor":"#1e1e1e","mainBkg":"#fff4e6","nodeBorder":"#1e1e1e","clusterBkg":"#f8f9fa","edgeLabelBackground":"#ffffff","classText":"#1e1e1e"}}}%%
flowchart LR
    W["Walk metadata - path, size, inode"] --> S["Group by size - drop unique"]
    S --> P["Partial hash - first and last 4 KB"]
    P --> F["Full hash - SHA-256 / BLAKE3"]
    F --> G["Duplicate groups"]
    G --> A["Report / dedup / hard link"]
from collections import defaultdict
import hashlib, os

def find_duplicates(paths):
    by_size = defaultdict(list)
    for p in paths:
        by_size[os.path.getsize(p)].append(p)
    groups = []
    for size, files in by_size.items():
        if len(files) < 2: continue
        by_head = defaultdict(list)
        for p in files:
            with open(p, 'rb') as f: by_head[f.read(4096)].append(p)
        for cands in by_head.values():
            if len(cands) < 2: continue
            by_full = defaultdict(list)
            for p in cands:
                h = hashlib.sha256()
                with open(p, 'rb') as f:
                    for chunk in iter(lambda: f.read(1 << 20), b''): h.update(chunk)
                by_full[h.hexdigest()].append(p)
            groups += [g for g in by_full.values() if len(g) > 1]
    return groups

Scaling to Many Machines

Think of it as a MapReduce-style job with three rounds:

  1. Scan: many workers walk different parts of the namespace (split by folder or bucket prefix) and emit (size, path).
  2. Shuffle by size: all files of the same size go to the same reducer. Unique sizes are dropped.
  3. Hash: for candidate groups, the workers closest to the data (same storage node) compute partial and then full hashes, and emit (hash, path).
  4. Shuffle by hash → duplicate groups.

Processing data where it lives avoids sending petabytes over the network. Only small hashes travel.

Deep Dive A — Comparing a billion files without reading them allDeep dive

Two files are duplicates only if their bytes match. The obvious ways to establish that all involve reading petabytes, and the design is mostly about avoiding that.

Weak

Compare every pair

For each pair of files, compare byte by byte.

Architecture diagram
%%{init: {"look":"handDrawn","handDrawnSeed":7,"theme":"base","fontFamily":"Virgil, \"Segoe Print\", \"Comic Sans MS\", cursive","themeVariables":{"fontFamily":"Virgil, \"Segoe Print\", \"Comic Sans MS\", cursive","fontSize":"16px","primaryColor":"#fff4e6","primaryBorderColor":"#1e1e1e","primaryTextColor":"#1e1e1e","secondaryColor":"#e7f5ff","tertiaryColor":"#ebfbee","lineColor":"#1e1e1e","textColor":"#1e1e1e","mainBkg":"#fff4e6","nodeBorder":"#1e1e1e","clusterBkg":"#f8f9fa","edgeLabelBackground":"#ffffff","classText":"#1e1e1e"}}}%%
flowchart LR
  N["1 billion files"] --> P["About 5 x 10^17 pairs"]
  P --> IO["Each pair re-reads both files"]
  IO --> NEVER["Never finishes - the arithmetic is the answer"]

Quadratic in the number of files, and every comparison re-reads data. This rung is worth ten seconds in an interview purely to make the next one obviously necessary.

Good

Hash every file and group by hash

Read each file once, compute a strong hash, group files that share one. Linear in the data instead of quadratic in the pairs.

The cost is now exactly one full read of the entire corpus — petabytes through the disks and the hash function, every run. That is hours to days of I/O, and the great majority of it is wasted: most files have a size no other file shares, so they could never have been duplicates and their bytes never needed reading.

Best

Eliminate by cheap signals first, then hash what is left

Three passes, each one only touching what survived the last:

Architecture diagram
%%{init: {"look":"handDrawn","handDrawnSeed":7,"theme":"base","fontFamily":"Virgil, \"Segoe Print\", \"Comic Sans MS\", cursive","themeVariables":{"fontFamily":"Virgil, \"Segoe Print\", \"Comic Sans MS\", cursive","fontSize":"16px","primaryColor":"#fff4e6","primaryBorderColor":"#1e1e1e","primaryTextColor":"#1e1e1e","secondaryColor":"#e7f5ff","tertiaryColor":"#ebfbee","lineColor":"#1e1e1e","textColor":"#1e1e1e","mainBkg":"#fff4e6","nodeBorder":"#1e1e1e","clusterBkg":"#f8f9fa","edgeLabelBackground":"#ffffff","classText":"#1e1e1e"}}}%%
flowchart LR
  ALL["1 billion files"] --> SZ["Group by size - metadata only, no reads"]
  SZ --> UNIQ["Unique size - cannot be a duplicate, dropped"]
  SZ --> SAME["Same size - a small fraction"]
  SAME --> HEAD["Hash the first 4 KB"]
  HEAD --> DIFF["Different prefix - dropped"]
  HEAD --> CAND["Same prefix - candidates"]
  CAND --> FULL["Full strong hash, then byte compare inside a group"]
  FULL --> DUP["Duplicate groups"]
  • Size first. It comes from the metadata walk — no file contents read at all — and it removes the overwhelming majority of files, because sharing an exact byte count is already rare.
  • A prefix hash next. Reading 4 KB costs about one disk seek, versus reading a whole file. Files that differ at all usually differ early; this clears most of the remaining candidates for almost nothing.
  • Full hash only for survivors, then an actual byte comparison within each group. Use a fast hash (BLAKE3, xxHash) for the earlier rounds and a strong one at the end — and do the byte compare, because grouping people's files by a hash collision is a data-loss bug, not a statistical curiosity.

Then measure which stage is actually the limit before optimising further. If the metadata walk dominates, parallelise by directory and use bulk listing (an S3 inventory report instead of a listing crawl). If disk I/O dominates, read sequentially with large buffers and avoid many threads seeking on one spindle. If CPU dominates, the disks are idle and a faster hash is the fix. Report throughput per stage — files/sec and MB/s — so the next change targets the real bottleneck rather than the one you assumed.

Deep Dive B — Correctness and incremental runsDeep dive

  • Files changing during the scan: record size + mtime at scan time, and re-check them before and after hashing. If they changed, re-hash or skip, and mark as unstable.
  • Incremental re-scan: keep a cache (path, inode, size, mtime) → hash. Next time, only re-hash files whose metadata changed.
  • Safe action on duplicates: never delete automatically without a policy. Options: report only, replace with hard links (same file system), or deduplicate in a content-addressed store with reference counts (see the object storage design).

Trade-offs & AlternativesTrade-offs

DecisionChoiceWhyAlternative
FilteringSize → partial hash → full hashReads a small fraction of dataFull hash every file: huge I/O
HashSHA-256/BLAKE3 for the final stepCollisions practically impossibleMD5: faster but broken for adversarial data
DistributionShuffle by size, then by hashOnly candidates get comparedCompare all pairs: impossible
PlacementHash where the data livesAvoids network transferCentral hashing: network bottleneck

Common Follow-up QuestionsFollow-ups

  • "Near-duplicates (same photo, different compression)?" Use perceptual hashes or embeddings and similarity search instead of exact hashes.
  • "Chunk-level duplicates inside large files?" Split files into content-defined chunks (rolling hash) and deduplicate chunks, as backup systems do.
  • "Memory for billions of entries?" Stream and sort to disk (external sort by size, then by hash) instead of holding everything in hash maps.

Wrap-UpWrap-up

Walk metadata only, group by size, then split groups by a cheap partial hash and finally a strong full hash, so only real candidates are ever fully read. Scale it as scan → shuffle by size → hash near the data → shuffle by hash. Measure each stage to find whether disk, CPU, metadata or network is the limit, re-check files that changed, and cache hashes for incremental runs.

More Case Studies

Frequently Asked Questions

What is the Duplicate File Detection at Scale system design question?

Duplicate File Detection at Scale is a system design interview question asked at FAANG companies. It covers storage, algorithms, distributed systems and tests your ability to design scalable, production-ready systems. InterviewSkool's breakdown walks you through requirements, API design, architecture, and trade-offs.

Which companies ask the Duplicate File Detection at Scale question?

Anthropic, OpenAI have reportedly asked variations of this question in system design interviews. The exact wording may differ, but the core design challenges remain the same.

How should I prepare for the Duplicate File Detection at Scale interview question?

Start with the problem statement and scale estimates, then design the high-level architecture. Focus on the core components, data model, and API design. InterviewSkool's breakdown covers the full solution with mermaid diagrams and trade-off analysis to help you prep efficiently.

What level is the Duplicate File Detection at Scale question?

This question is suitable for SDE-2, SDE-3, and Staff engineer interviews. The level guidance on this page provides specific tips for each level — SDE-2 candidates should focus on core architecture, while Staff engineers should discuss trade-offs, monitoring, and incremental rollouts.

Practice with a Mock Interview

Apply what you learned in a live system design mock interview with InterviewSkool's AI interviewer.

Start System Design Interview →