Build an Offline Duplicate File Scanner
Group equal byte contents by path and explain how to extend the algorithm to real files.
Example check: Worked and guided code samples are checked against CPython 3.13. Practice uses Pyodide 0.29.5 (Python 3.13) in your browser. How we check lessons
Video lesson: Build an Offline Duplicate File Scanner
The preview is stored on this site. YouTube loads only when you press Play. Watch on YouTube
Jump to a chapter
Read the video transcript
Select a timestamp to watch that moment on YouTube.
- 00:00
A duplicate scanner should identify files with the same bytes, regardless of their names. The browser project models a directory as a mapping from path labels to byte strings. That gives us a deterministic, offline test fixture before we discuss reading actual files. The first example hashes the same byte sequence twice and compares the hexadecimal digests. Equal contents produce equal digests, so a dictionary keyed by digest can collect paths that might match. Hashing is valuable for large files because a fixed-size digest can be stored instead of keeping all contents in a grouping key. A scanner should identify candidate duplicates and report their paths, not
- 00:42
silently remove anything. This video stays offline with a dictionary of byte strings, so we can see every input and result. Predict which path names should be paired when a.txt and b.txt contain identical bytes but c.txt does not. A count of distinct contents is insufficient: the user needs the actual path group to inspect before taking any action. A naive implementation stores byte contents in a set. It can say how many unique values exist but loses the paths that need to be reported. Instead, group paths by a digest such as SHA-256, then compare bytes inside each
- 01:24
digest group before declaring equality. A digest is an efficient candidate key, not a mathematical proof that two inputs are equal. A digest collision remains possible, so this project treats equal hashes as candidates and verifies the original bytes inside each bucket. With the tiny in-memory fixtures used by the browser task, that second comparison is cheap. The design is faithful to a real scanner without claiming cryptographic impossibility of collisions. The naive starter returns only a number of unique contents. Even if that number is correct, it cannot tell the user which paths are duplicates.
- 02:06
We need to carry identifiers through the grouping process. The starter's numeric result may look plausible, yet it discards every path label. The observable failure is missing information, not an exception. Keep the mapping visible so we can compare the desired duplicate group with the scalar that was returned. Return only groups with at least two matching files. Sort path names within each group and sort the groups by their first path so output is stable across dictionary insertion orders. Empty files can be duplicates too; one file alone is never a duplicate group. Treat path labels as names in this browser exercise and do not access the user's
- 02:47
actual disk. The second example uses different file names with identical bytes. A set of byte values would know there is one distinct content value, but the user needs the two path labels to inspect the duplicates. Maintain those labels all the way through grouping and output. Do not merge files merely because their names or extensions match, and do not treat two different names as proof of different contents. The scanner's contract is exact byte equality. It does not normalize line endings or encodings; two text files that read similarly but differ in bytes are not duplicates under this definition.
- 03:29
A digest makes a compact candidate key, but a digest match alone is not proof. The diagnostic comparison shows that different names can have equal bytes. The algorithm checks actual contents within each candidate bucket. A SHA-256 digest is a fast candidate bucket. Within that bucket compare the original bytes before grouping, because equal digest values alone do not prove equality. Path names stay attached to their byte contents throughout the comparison. Implement find_duplicates(files), where files maps path strings to bytes. Return a sorted list of tuples of paths sharing identical bytes, omitting
- 04:09
singleton groups. Use SHA-256 as a candidate key and verify equal bytes within a digest bucket. The prepared example must print [('a.txt', 'b.txt')]. Build digest buckets with hashlib.sha256(data).digest(). Within each bucket, group by the actual bytes and keep groups with two or more sorted paths. The final example sorts paths before creating a tuple. The checked task sorts each duplicate group and the overall list by its first path. That makes output deterministic regardless of the insertion order of the input
- 04:49
mapping, which is helpful for review and automated checks. The on-screen fix now handles every digest bucket and every distinct byte sequence inside it. The exact dictionary uses the original bytes as keys, so even a digest collision cannot merge different contents. It sorts paths within each group and sorts the groups before reporting. Real files would be read in chunks with error handling. The corrected scanner builds digest buckets, then exact-byte groups within each bucket. It reports only groups of two or more and sorts their paths. That second grouping keeps distinct contents separate even if a test
- 05:29
deliberately forces a digest collision. A real file scanner would walk a chosen root with pathlib, reject symlink surprises according to a documented policy, hash large files in chunks, and handle permission or read errors explicitly. Do not automatically delete duplicates. This project stops at identification and shows a reproducible algorithm suitable for testing before any filesystem actions. No file is deleted or renamed by this project. A real scanner must choose a root directory, decide whether to follow symlinks, read large files in chunks, and handle permission failures.
- 06:09
Those operational choices need explicit user control. The in-memory algorithm offers a safe foundation before any disk traversal. Keep the human-readable identifiers alongside candidate digests. Hashes narrow candidate sets; an exact byte comparison confirms a group. The final run deliberately forces every file into one fake digest bucket. It still reports two separate duplicate groups: two empty files and two files containing X. The unrelated singleton is omitted. A real SHA-256 collision is extraordinarily unlikely, but a controlled
- 06:49
fake key proves the grouping logic does not assume it is impossible. No file is deleted by this exercise. For real disk scans, separate candidate detection from user-approved cleanup. The forced-collision check puts every item into one fake digest bucket. Two distinct byte values still produce two separate duplicate groups, and a singleton stays out. The scanner reports candidates and never deletes files; cleanup requires an explicit separate decision.
Understand the concept
A duplicate scanner should identify files with the same bytes, regardless of their names. The browser project models a directory as a mapping from path labels to byte strings. That gives us a deterministic, offline test fixture before we discuss reading actual files.
A naive implementation stores byte contents in a set. It can say how many unique values exist but loses the paths that need to be reported. Instead, group paths by a digest such as SHA-256, then compare bytes inside each digest group before declaring equality. A digest is an efficient candidate key, not a mathematical proof that two inputs are equal.
Return only groups with at least two matching files. Sort path names within each group and sort the groups by their first path so output is stable across dictionary insertion orders. Empty files can be duplicates too; one file alone is never a duplicate group. Treat path labels as names in this browser exercise and do not access the user's actual disk.
A real file scanner would walk a chosen root with pathlib, reject symlink surprises according to a documented policy, hash large files in chunks, and handle permission or read errors explicitly. Do not automatically delete duplicates. This project stops at identification and shows a reproducible algorithm suitable for testing before any filesystem actions.
- Group file paths rather than just values
- Use hashes to find candidates
- Confirm candidate contents byte-for-byte
- Sort results and never delete automatically
See it step by step
Read the code, predict the output, then compare it with the result.
01. Same bytes produce the same digest
import hashlib
a=hashlib.sha256(b'one').hexdigest()
b=hashlib.sha256(b'one').hexdigest()
print(a==b)TrueA digest can group candidate equal files.
02. Names can differ while bytes match
files={'a.txt':b'one','b.txt':b'one'}
print(files['a.txt']==files['b.txt'])
print('a.txt'=='b.txt')True
FalseContent equality is independent of a path label.
03. Sort a report
paths=['z.txt','a.txt']
print(tuple(sorted(paths)))('a.txt', 'z.txt')Stable ordering makes output and tests reproducible.
A closer look
Follow the reasoning, inspect each result, then try the suggested changes in the console below.
Hash bytes to find candidate matches
The first example hashes the same byte sequence twice and compares the hexadecimal digests. Equal contents produce equal digests, so a dictionary keyed by digest can collect paths that might match. Hashing is valuable for large files because a fixed-size digest can be stored instead of keeping all contents in a grouping key.
A digest collision remains possible, so this project treats equal hashes as candidates and verifies the original bytes inside each bucket. With the tiny in-memory fixtures used by the browser task, that second comparison is cheap. The design is faithful to a real scanner without claiming cryptographic impossibility of collisions.
import hashlib
a=hashlib.sha256(b'one').hexdigest()
b=hashlib.sha256(b'one').hexdigest()
print(a==b)True- Compute SHA-256 for each byte string.
- Collect path labels by digest.
- Compare original bytes within candidate buckets.
Report paths, not just a duplicate count
The second example uses different file names with identical bytes. A set of byte values would know there is one distinct content value, but the user needs the two path labels to inspect the duplicates. Maintain those labels all the way through grouping and output.
Do not merge files merely because their names or extensions match, and do not treat two different names as proof of different contents. The scanner's contract is exact byte equality. It does not normalize line endings or encodings; two text files that read similarly but differ in bytes are not duplicates under this definition.
files={'a.txt':b'one','b.txt':b'one'}
print(files['a.txt']==files['b.txt'])
print('a.txt'=='b.txt')True
False- Compare the two byte values.
- Compare the two path labels.
- Keep both labels in the duplicate group.
Sort a stable, non-destructive report
The final example sorts paths before creating a tuple. The checked task sorts each duplicate group and the overall list by its first path. That makes output deterministic regardless of the insertion order of the input mapping, which is helpful for review and automated checks.
No file is deleted or renamed by this project. A real scanner must choose a root directory, decide whether to follow symlinks, read large files in chunks, and handle permission failures. Those operational choices need explicit user control. The in-memory algorithm offers a safe foundation before any disk traversal.
paths=['z.txt','a.txt']
print(tuple(sorted(paths)))('a.txt', 'z.txt')- Sort names inside each matching group.
- Omit one-file groups.
- Return a report and leave input data untouched.
Try it in Python
Edit the example and run it. Python starts in your browser the first time you click Run.
Python console
Ready to runNeed input()? Add one value per line
Your output appears here.
Need a hint?
Build digest buckets with hashlib.sha256(data).digest(). Within each bucket, group by the actual bytes and keep groups with two or more sorted paths.
Complete the task and select Check task to verify your code.
Quick quiz
Three questions. You can change your answers and try again.
Typical mistakes
Everyone meets these errors. See what causes them and how to fix them.
Discarding the path labels
len(set(files.values()))Group paths by contentWhat happens: The result cannot say which files match.
Keep the human-readable identifiers alongside candidate digests.
Treating digest equality as certain equality
if hash_a == hash_b: duplicateConfirm the original bytes matchWhat happens: A collision could create a false duplicate.
Hashes narrow candidate sets; an exact byte comparison confirms a group.