How Text Diff Works and How to Read One
How diff tools find changes with the longest common subsequence, how to read unified diff output, and when line diffs mislead you.
Editorial disclosure: Pyalm publishes and maintains the products and free tools discussed on this site. Regulatory statements are linked to primary sources where applicable. No independent professional review is claimed unless a reviewer is explicitly named.
A diff shows what changed between two versions of text. You use one every time you review a pull request, compare configuration files, or check what a colleague changed in a document. Knowing how diffs work makes them far easier to read — and tells you when to distrust them.
The idea: longest common subsequence
Given two lists of lines, a diff finds the longest common subsequence (LCS) — the largest set of lines that appear in both, in the same order. Everything in the original that isn't in that set was removed; everything in the new version that isn't in it was added. Lines that moved appear as a removal in one place and an addition in another.
Git uses efficient variants (Myers' algorithm, and options like patience and histogram) that find a minimal or more readable set of edits. Different algorithms can produce different, equally valid diffs of the same change.
Reading unified diff output
@@ -1,5 +1,6 @@
server {
- listen 80;
+ listen 443 ssl;
server_name example.com;
- root /var/www/html;
+ root /var/www/app;
+ ssl_certificate /etc/ssl/example.pem;
}
- Lines starting with a space are unchanged context.
- Lines starting with - exist only in the original.
- Lines starting with + exist only in the new version.
- @@ -1,5 +1,6 @@ says the hunk starts at line 1 and spans 5 lines in the original and 6 in the new.
A changed line shows as a minus followed by a plus. The eye has to compare them — which is why word-level or character-level highlighting helps on long lines.
Options worth knowing
| Option | What it does | Use when |
|---|---|---|
| Ignore whitespace | Treats runs of spaces and tabs as equal | Re-indented code, tab/space conversion |
| Ignore case | Compares case-insensitively | Config where case doesn't matter |
| Ignore blank lines | Skips empty line changes | Reformatted documents |
| Word diff | Marks changed words inside a line | Prose, long lines, minified output |
| Context lines | Shows N unchanged lines around each change | Large files |
In git: git diff -w ignores whitespace, git diff --word-diff highlights words, and git diff --stat summarises.
When line diffs mislead
- Reformatting hides real changes. One logic change in a file whose indentation changed everywhere is almost unreadable. Format first, in a separate commit.
- Minified or single-line files differ completely on every change; use a word diff or beautify both versions first (see the Code Minifier).
- Reordered items (keys in JSON, entries in a list) show as add/remove pairs. For JSON use a structural compare like the JSON Compare and read how to compare two JSON files.
- Line endings. CRLF vs LF can make every line look changed.
- Binary and generated files don't diff meaningfully.
Practical uses beyond code
- Compare contract versions or policy documents.
- Check what changed in a configuration before deploying.
- Compare two exports of the same report — though for accounting data a keyed comparison (match by invoice number) is better than line order.
- Review copy edits.
Try one
Paste two versions into the free Text Diff Checker to see added and removed lines with line numbers, ignore whitespace or case, show only changes, and copy the result as a unified diff for a ticket or review comment. It runs in your browser and never uploads the text.
Automate what you repeat
If your team compares exports, reconciles lists or checks config drift by hand every week, that's a good candidate for a small internal tool or integration. See workflow automation for SMBs or discuss a build with Pyalm.