Text Diff
Compare two texts line by line, with word-level detail on the lines that changed. Sees through line-ending and whitespace differences, and tells you when a diff is too large to be exact rather than pretending otherwise.
compare
| Change | orig | new | line |
|---|---|---|---|
| removed | 1 | function total(items) { | |
| added | 1 | function total(items, currency = "EUR") { | |
| unchanged | 2 | 2 | let sum = 0 |
| unchanged | 3 | 3 | for (const item of items) { |
| removed | 4 | sum += item.price | |
| added | 4 | if (item.price == null) continue | |
| added | 5 | sum += item.price * rateFor(currency) | |
| unchanged | 5 | 6 | } |
| removed | 6 | return sum | |
| added | 7 | return round(sum, 2) | |
| unchanged | 7 | 8 | } |
Runs in this browser tab. Nothing is uploaded, stored or logged — which matters here more than for most tools, since the things people diff are often their own configuration and their own source.
How to use this tool
A diff is the shortest description of what changed between two things, and it is genuinely hard to do well. The output looks simple - a column of signs and a column of text - and almost every surprising thing it does comes from a decision that had to be made somewhere and is not visible in the result.
- 1Paste the original on the left and the changed version on the right. The result updates as you type.
- 2**unified** puts one document in one column and is the default because it works on a phone. **split** puts them side by side and scrolls horizontally rather than compressing.
- 3Tick **ignore whitespace** or **ignore case** when the change you care about is not formatting.
Line endings are the first thing that goes wrong
A file saved on Windows has a carriage return and a newline at the end of each line. A file saved on Linux has just the newline. The two files look identical in every editor worth the name, and a diff that compares them literally reports that every single line changed.
That is not a rare edge case. It is what a file looks like after one person edits it on a different operating system from everyone else, and it buries the three lines that actually differ under four hundred that did not. This page treats CRLF, LF and a lone CR as one line ending, which is why you will not see it here.
one\ntwo\nthree Unix
one\r\ntwo\r\nthree Windows
onetwothree classic Mac
All three are the same three lines. A tool that does not normalise the
terminator reports eighteen changes between any two of them.A moved line is a deletion and an insertion
Move the first line of a three-line file to the end and the minimal description is: delete one line, add one line. It is not 'moved', because producing a move requires knowing that the line is the same line and that its new position is meaningful — and a line of text has no identity beyond its content.
one one
two -> three
three two
A diff reports one removal and one addition. "Changed 2 lines" is
the honest summary, and it is what git says too, unless you ask
git about renames - which it can only do for whole files.So do not read a diff as a list of edits somebody made. Read it as the shortest set of line replacements that turns one document into the other. It tells you *what* is different. It cannot tell you *why*, and nothing on this page pretends otherwise.
The shortest diff is a choice, not a fact
When two documents share lines, there is usually more than one way to line them up. Some of those ways are shorter than others, and the shortest ones are what every tool aims for. But when two alignments are *equally* short, which one you get is arbitrary — and different tools choose differently.
a b c a b c
b c d vs x b c d
Both keep three lines in common. Both are one insertion. The first
inserts "x" at the end, the second at the front, and a diff that
reports the second looks wrong until you check that it is not.This page's engine is an exact longest-common-subsequence search. Python's `difflib`, which is what most people compare against, uses a different algorithm and its own documentation says the result is not guaranteed minimal. On a set of 167 generated cases this page agrees with it exactly on 89% of them and is never worse — on one case it finds a strictly shorter edit script.
When a diff is too big, this page says so
The exact algorithm is quadratic. Two documents of a few thousand lines each are fine; two documents of fifty thousand are not, because the search wants a table with billions of entries. Rather than freeze the tab, this page does something coarser and **tells you on screen that it has**.
- It first breaks the problem up on lines that appear exactly once in each document. Two versions of a source file usually share a handful of those, and using them as anchors turns one huge search into several small ones — which usually means the answer is still exact.
- Only if a region is still too large does it fall back to showing that region as one removed block and one added block. The status bar reads `coarse` and a caveat sits above the result.
Whitespace and case
The two toggles exist because reformatting a file and changing it are different intentions, and a diff that cannot tell them apart makes both harder to see.
diff("a b", "a b", { ignoreWhitespace: true })
// 0 added, 0 removed, 1 unchanged
//
// and the line is still DISPLAYED as "a b", not as the
// normalised "a b" that was compared
diff("Hello", "hello", { ignoreCase: true })
// 0 added, 0 removed, 1 unchanged - shown as "Hello"That last detail is the one worth insisting on. The comparison runs over a normalised key, but what you read is the original text. A tool that normalised first and displayed the result would mark a line as unchanged while showing you different characters than the file contains.
Unified or side by side
Both are here because both have a real use, and the default is the one that works everywhere.
- **unified** is one column: every line of both documents in order, marked. It is the default because it fits a phone, and because a single column cannot fall out of alignment. Most diff tools default to this too, and reading a change top to bottom without tracking two cursors is genuinely easier.
- **split** is two columns, aligned row by row, which is what you want when you are looking for one specific change in a large file and do not want to keep losing your place. On a narrow screen it scrolls horizontally rather than compressing, because a two-column view that squeezes its columns has lost the only information it was adding.
In the split view, a line that was replaced shows its changed words highlighted. That refinement runs only on replaced lines, never across the whole document — it is a second quadratic pass, and running it everywhere would be both slow and useless, since nothing is interesting about highlighting words in a line that did not change.
What this page is not
It does not merge. It will not tell you which side is right, or produce a third document that is the two of them combined, or check that the result compiles. A merge tool needs to know what a line *means* in a way that a text comparison cannot.
It does not do character-level diffs across the whole file, because that is a different question and usually the wrong one. If you are comparing two minified bundles, a character diff is what you want and this is not the tool.
Doing it yourself
For anything you have on disk, use the real thing. `git diff` understands renames, has a word-diff mode, colours by author, and does not have a size limit worth mentioning.
git diff --stat # a summary, not 4,000 lines
git diff --word-diff # word-level, across the whole file
git diff -w # ignore whitespace, like the toggle
git diff --ignore-all-space # the same thing, more explicit
diff -u a.txt b.txt # the classic, no repository needed
# and for two files that are not in git at all
git diff --no-index a.txt b.txtAnd the algorithm itself, if you need it in code. Every mainstream language has one in the standard library, and they are all at least as good as what is in this page.
// No built-in line diff, so the usual answer is a longest
// common subsequence over arrays of lines. Python has one in
// the standard library, which is worth remembering before
// reaching for a dependency:
import difflib
difflib.unified_diff(a.splitlines(), b.splitlines(), lineterm="")Frequently asked questions
- Why does my diff differ from git's?
- Usually tie-breaking: when two ways of lining the documents up are equally short, tools pick different ones. Check the number of added and removed lines — if this page's total is smaller, it found the shorter answer. If it is larger, that would be a bug worth reporting.
- Why does it say coarse?
- One region of the pair was too large for the exact search, so that region is shown as a whole removed block and a whole added block. Read it as 'this page did not look that closely', not as 'these lines all changed'. A local git diff will handle it properly.
- Does it merge the two documents?
- No, and it will not pretend to. Producing a merged document requires knowing what each line is for, which a text comparison cannot know. That is a job for a merge tool, and a diff tool that offered to do it would be guessing.
- Why is a moved line reported as a change?
- Because a line of text has no identity beyond its content, so there is no way to know it moved rather than being deleted and something else written. Git can detect renames between whole files; within a file it reports a move as a deletion and an insertion too.
- What does ignore whitespace actually ignore?
- Every run of whitespace is collapsed to a single space and the ends are trimmed. So a change in indentation and a change from a space to a tab both disappear. That is what you want when asking 'did the logic change', and not what you want when asking 'did the reformatting change anything' — which matters in languages where indentation is syntax.
- Does this send my text anywhere?
- No. It runs in your browser tab and nothing is uploaded, stored or logged. Worth being emphatic about here, because what people paste into a diff is often their own configuration or their own source, and a diff shows all of it at once.