Line Diff Explained: How File Comparison Works, Hunks, Moved Lines and Whitespace
Most file comparison tools, from GNU diff to Git to online diff checkers, treat a file as a list of lines and compare those lists. That simple model explains nearly everything they do: why editing one character marks a whole line as changed, why reformatting a file makes the diff explode, and why a moved function appears as a deletion in one place and an addition in another.
Lines are the unit of comparison
A line diff splits each file on newline characters and compares the two lists of lines. Two lines either match exactly, byte for byte, or they do not. There is no notion of "almost the same line", so a single changed character produces a removed line and an added line.
old.txt new.txt
1 name: api 1 name: api
2 replicas: 2 2 replicas: 3 <- one character differs
3 port: 8080 3 port: 8080
diff:
name: api
- replicas: 2
+ replicas: 3
port: 8080
The algorithm then finds the longest common subsequence of lines, meaning the lines that appear in both files in the same order, and reports the rest as deletions and insertions. Git and GNU diff use Myers' algorithm for this by default, which finds a shortest edit script efficiently when the files are similar.
Lines make a good unit for code and configuration, because a line usually holds one statement or setting. They are a poor unit for prose, where a paragraph is often one long line; a word-level diff suits text better.
Hunks and context
Changed lines are grouped into hunks. Each hunk shows the changed lines plus a few unchanged lines around them, three by default in unified diff, so a reader, or a patch tool, can see where the change belongs.
@@ -40,7 +40,8 @@ def load_config(path):
with open(path) as f:
data = yaml.safe_load(f)
- return data
+ validate(data)
+ return data
...
The header -40,7 +40,8 means the hunk covers 7 lines starting at line 40 in the old file and 8 lines starting at line 40 in the new one. The text after the second @@ is a hint about the enclosing function, which Git finds using language-aware patterns.
Changes closer together than twice the context size merge into one hunk. git diff -U10 shows more context around each change, and -U0 shows none, which is useful for scripts that only need the changed lines.
Moved lines and why they look like two changes
A basic line diff has no concept of moving. Moving a block from the top of a file to the bottom shows as a deletion at the top and an insertion at the bottom, and a reviewer has to check by eye that the two blocks are identical.
Git can detect this. git diff --color-moved highlights moved blocks in different colours from real additions and removals, and --color-moved-ws=ignore-all-space still recognises them when indentation changed. Some online tools offer a similar option. When you move and edit code in the same change, consider splitting it into two commits: a pure move, then the edit.
Line alignment can also go wrong without any moves. When lines such as } or blank lines repeat, the algorithm may match the wrong copies and produce a confusing diff that is technically minimal. git diff --histogram or --patience anchors on lines that occur only once and usually gives a more natural result.
Whitespace and line endings
Because lines must match exactly, invisible differences count. Re-indenting a block, converting tabs to spaces, or stripping trailing whitespace marks every affected line as changed, and can hide one real change among hundreds of cosmetic ones.
diff -w old.py new.py # ignore all whitespace
diff -b old.py new.py # ignore changes in the amount of whitespace
git diff -w # same in Git
git diff --ignore-blank-lines
git diff --ignore-cr-at-eol # ignore CRLF vs LF
Line endings are the classic whole-file diff. A file saved on Windows ends each line with CRLF (\r\n), the same file saved on Linux or macOS with LF (\n), so every line differs by an invisible \r. Git's core.autocrlf setting and a .gitattributes file with * text=auto normalise line endings in the repository and prevent this.
A missing newline at the end of the file shows as \ No newline at end of file in unified diffs. The last line counts as changed even though its visible text is identical.
Comparing files online and on the command line
To compare two files online, paste their contents into the diff checker. It compares line by line, which suits code, configuration and data; for documents, put each sentence on its own line first, or run git diff --word-diff locally. Because the comparison happens in the browser, the files are not uploaded, which matters for configuration with credentials and anything containing personal data.
diff -u old.conf new.conf # unified diff, any Unix system
diff -y --width=160 a.txt b.txt # side by side
git diff --no-index a.txt b.txt # Git's diff on any two files
vimdiff a.txt b.txt # interactive
code --diff a.txt b.txt # VS Code
fc /n a.txt b.txt # Windows cmd
For very large files, such as logs or database dumps, line diff tools can be slow or run out of memory in a browser. Reduce the input first: sort and deduplicate if order does not matter, filter out timestamps, or compare checksums of chunks to find the region that differs.
Binary files and structured data
Line diff is meaningless for binary files. Images, PDFs and archives contain bytes that happen to include newline values at arbitrary positions. Git detects binary content, typically a NUL byte in the first few kilobytes, and prints Binary files a and b differ instead. For those files, compare checksums to know whether they differ, and use a format-specific tool to see how.
Structured text such as JSON, XML or CSV diffs well only when both sides are formatted the same way. Minified JSON is one line, so any change marks the whole document. Pretty-print both sides with the same indentation and sorted keys first. Git can do this automatically with textconv drivers configured in .gitattributes.
Three-way diffs and merge conflicts
Merging needs three versions: the common ancestor and the two changed copies. Lines changed on only one side are taken automatically. Lines changed differently on both sides are a conflict, which Git marks in the file.
<<<<<<< ours
replicas: 3
||||||| base
replicas: 2
=======
replicas: 4
>>>>>>> theirs
The middle base section appears when merge.conflictStyle is set to diff3 or zdiff3. It shows what the line looked like before either change, which makes most conflicts much easier to resolve. Because merging is line-based too, two edits to different parts of the same line always conflict, even when they do not overlap logically.
Sources and further reading
Eugene W. Myers, "An O(ND) Difference Algorithm and Its Variations" (1986), describes the core algorithm. The GNU diffutils manual documents unified format, -w, -b and -y. The Git documentation covers --color-moved, --histogram, --patience, .gitattributes and merge.conflictStyle.
Frequently asked questions
What is a line diff?
A comparison that treats each file as a list of lines and reports which lines were removed and which were added. Lines match only if they are byte-for-byte identical, so any change inside a line shows as that line being replaced.
How do I compare two files online?
Paste the contents of both files into a browser-based diff checker and read the highlighted lines. For prose, split sentences onto separate lines first or use a word-level diff such as `git diff --word-diff`. For confidential files, make sure the tool runs locally so nothing is uploaded.
Why does diff show the whole file as changed?
Usually different line endings (CRLF vs LF), re-indentation, or one side being minified onto a single line. Ignore whitespace or CR at end of line, or normalise formatting on both sides, then compare again.
What does @@ -40,7 +40,8 @@ mean?
It is a hunk header: the hunk covers 7 lines starting at line 40 of the old file and 8 lines starting at line 40 of the new file. The lines below it are context (space), removals (`-`) and additions (`+`).
How can I see moved lines in a diff?
Plain diff shows a move as a deletion plus an addition. In Git use `git diff --color-moved`, which colours moved blocks differently from real changes.
Can I diff binary files line by line?
No. Binary files have no meaningful lines; diff tools report only that they differ. Compare checksums to detect differences and use a format-specific tool to inspect them.