Diff Checker
Paste or drop two blocks of text and highlight the exact words or characters that changed inside each edited line. Ignore case or whitespace, all in your browser.
- Free, no account
- No watermark
- No usage limit
About the Diff Checker
Change 3000 to 3500 in one config line and most free diff tools paint the whole old line red and the whole new line green, then leave you to find the digit. This one narrows the marking down to the part that actually moved. In Word mode that edit comes back as four pieces, with 3000; marked and const timeout = left alone. Switch to Character and it narrows again, to a single 0 swapped for a 5.
Two things on this page you will not get from the other diff sites. I timed the comparison at eight sizes and wrote down where it stops feeling instant, and I caught this tool telling a lie about a config file, measured what it would cost to fix, and fixed it. Both are below, with the actual output, including what the old version printed. Nothing you paste is uploaded, and across every control I pressed while testing, the tool wrote zero entries to browser storage.
How to use
- Put the original on the left. Paste it, drop a file onto the box, or click the load link under it. Source code, YAML, CSV rows, log output, prose, anything that breaks into lines.
- Put the new version on the right. Order matters. The left box is the baseline, so whatever the right box adds shows green and whatever it drops shows red. Loaded them backwards, hit Swap sides and the colors flip.
- Pick a highlight level. Word is the default. Character narrows to single letters and digits. Line turns the inside-the-line highlighting off and marks whole lines, which reads like a plain git diff.
- Read the diff below. It runs top to bottom in document order. An edited line shows the old version above the new one, with the changed parts picked out.
- Copy or download. Copy diff puts a plain text version on your clipboard and Download .diff saves it as a file. Both use
+and-prefixes.
The same line under all three grains
Here is one worked pair, and the three answers it produces. Original on top, changed below:
const timeout = 3000; // milliseconds
const timeout = 3500; // milliseconds
Line gives you the whole line twice, one red and one green, no inside-the-line marking at all.
Word breaks it into four pieces and marks the middle two:
equal "const timeout = "
removed "3000;"
added "3500;"
equal " // milliseconds"
Notice the semicolon rode along with the number. A word here is a run of characters with no space in it, so punctuation glued to a number is part of that word. The spaces between words are separate pieces of their own, which matters later.
Character goes further and lands somewhere you might not predict:
equal "const timeout = 3"
removed "0"
added "5"
equal "00; // milliseconds"
It matched the leading 3 and the trailing 00, so the single character it marks is the second digit rather than the first one you would have pointed at. The result is correct, it is just aligned from both ends inward.
For prose and for most code, Word is what you want. Character is for the change that hides, one flipped digit in a long hash, or a run of full width digits pasted out of a spreadsheet. I checked that last one, 2026 against its full width twin comes back with all four digits marked, because not one of them is the character it looks like. Line is better than it sounds for config files, because a line there is usually one complete thought and you would rather read the whole new line in context.
Where it stops being instant
The comparison lines up every line on the left against every line on the right to find the longest run the two files share. That grid is the cost, and it gets built at full size whether the files match or not. At the tool's own cap of 5,000 lines a side, that is 25,000,000 cells.
I timed the line alignment at eight sizes. These are Node timings on a desktop, not browser timings, so read the shape of the curve and not the numbers.
| lines per side | grid cells | time |
|---|---|---|
| 500 | 250,000 | 8 ms |
| 1,000 | 1,000,000 | 24 ms |
| 2,000 | 4,000,000 | 58 ms |
| 5,000 | 25,000,000 | 515 ms |
Double the file and the work roughly quadruples. Under about 2,000 lines a side you will not notice it. At 5,000 that same run took 193 MB of memory, which is the part that decides whether a phone copes.
The cap is 5,000 lines per side. At exactly 5,000 it runs. At 5,001 you get "Input is too large (limit 5,000 lines per side). Please reduce the text size." instead of a diff. I pushed past the cap on purpose to see what it is protecting: 10,000 lines a side is 100,000,000 cells, and that run took 1.5 seconds and 766 MB. A browser tab holding 766 MB for one comparison is not something you want to hand a phone. For what it is worth, 5,000 lines is about 67 KB of short config lines, 262 KB of ordinary source, or 575 KB of prose, so the cap is generous for the kind of thing people actually paste.
There is a second limit, and it bites one enormous line rather than a big file. Inside an edited pair, the same grid gets built out of that line's own pieces, and once it passes 4,000,000 the tool gives up on the inline marking and shows a plain red-over-green pair instead. In Character mode that lands at 2,000 characters. I measured 2,000 a side still highlighting in 22 ms, and 2,001 falling back instantly.
In Word mode it lands at 1,000 words, not 2,000, and the reason is the spaces. Every gap between two words is a piece in its own right, so 1,000 words comes to 1,999 pieces while 1,001 words comes to 2,001, which is the one that tips it over the limit. A minified file on a single line will always fall back. That is deliberate, and it is why a giant paste cannot lock your tab.
Line endings, tabs and the blank line nobody thinks about
Windows files end lines with a carriage return and a newline, Linux and Mac files use just the newline, and a stricter tool flags every single line as changed over that invisible difference. This one does not, and I checked rather than assuming: I saved the same three lines both ways, compared them with every toggle off, and the bar came back "Files are identical."
The one that does bite is subtler. If one side ends with a newline and the other does not, that trailing newline creates a final empty line, so you get +1 added and a blank green row at the bottom of an otherwise clean diff. The Ignore whitespace box makes it go away, with a note under the counters that reads "Ignore whitespace left 1 blank line out of the comparison, all on the right."
That toggle handles four kinds of invisible change, and I checked each one separately. A trailing space, a tab standing in for four spaces, a doubled space inside a line, and a leading space all read as changed with the box off and as identical with it on. It also drops blank lines out of the comparison completely, which is the case that comes up most: a formatter that removed the blank line between every block scored -2 removed with the box off, and with it on gave "Files are identical. Ignore whitespace left 2 blank lines out of the comparison, all on the left."
Leave it off when the spacing is the thing you are checking. A YAML file, a Makefile, a Python file, anywhere indentation carries meaning and a stray tab is a real bug.
A moved block, and the lie I found in my own tool
It does not report a move. A moved paragraph comes back as a removal where it used to be and an addition where it landed, with everything in between left untouched.
What surprised me is which block gets named. Move one line down past two others and it names that line, which is what you would expect. Move three lines past one, and it names the single line as having moved instead of the three, because the longest run of matching lines it can find is the three, so the cheaper story is that the single line jumped over them. Both readings describe the same edit correctly. Just do not read the red and green as "this is the block that moved".
Now the part I would rather tell you than have you take on trust, because it is about this tool being wrong. Until 28 August 2026, when a run of removed lines sat directly against a run of added lines, Word and Character mode paired them off by position: first with first, second with second, with nothing checking that the two lines had anything to do with each other. Delete one line and edit the line right underneath it, and it printed this:
- DEBUG = True
+ PORT = 9090
- PORT = 8080
HOST = "0.0.0.0"
Read literally, that says DEBUG = True became PORT = 9090 and PORT = 8080 was deleted. What actually happened is that the DEBUG line went away and the port number changed. Line mode got the same input right the whole time, because Line mode never pairs anything up, which is what told me the pairing and not the alignment was at fault. Here is the same input today:
- DEBUG = True
- PORT = 8080
+ PORT = 9090
HOST = "0.0.0.0"
Same three lines Line mode gives you, with 8080 and 9090 picked out inside the pair, which is the part Line mode cannot give you.
How it decides now, and what that cost
Every removed line in a block is scored against every added line on how many two-character runs they share, and a pair only forms above 0.35. I calibrated that on thirty pairs written out by hand, eighteen I knew were one line edited into the other and twelve I knew were strangers, and the two groups turned out to overlap: the weakest real pair scored 0.296 (ENV=production against ENV=staging) and the strongest stranger 0.316 (alpha = 1 against beta = 7), so no cutoff separates them cleanly. I put the line above both because the two mistakes are not the same size: a wrong pair states something false about your file, while a missed pair only declines to highlight and still prints both lines one under the other, which is what Line mode does anyway. So one of my eighteen real pairs falls the wrong side of 0.35 and comes out unpaired, and I would rather lose that one highlight than print a claim about your config file that is not true. The common case is exempt, because a block holding one removed line and one added line has no second candidate to get wrong: those two pair however unalike they are, and an ordinary one-line edit behaves as it always did.
Scoring every candidate against every other is quadratic, so I timed it. Node on a desktop, same as the table above, so read these against each other rather than as browser numbers. One block of 500 changed lines went from 0.4 ms to 2.9 ms, 1,000 changed lines from 0.7 ms to 10.8 ms, 2,000 from 1.5 ms to 40.6 ms and 0.9 MB more memory. Then I took the budget out and ran it at the 5,000 line cap, where a wholly rewritten file is one block of 10,000 changed lines and 25,000,000 candidate pairings: 1,131 ms, against 6.9 ms for the old pairing by position on the same input. A correct answer nobody waits for is not a fix, so the comparison gets a budget of 1,000,000 line pairings. Any single block up to 1,000 removed lines against 1,000 added is checked in full, more changed lines in one blob than I have ever pasted anywhere. Past it the rest falls back to pairing by position, and the bar says so and names the number: at 1,010 against 1,010 I get "1,010 pairs below were matched by position and never checked against each other, because this comparison went past 1,000,000 line pairings."
The part that is still not perfect
The pairing follows the order of your file. If the third removed line pairs with the fifth added line, the fourth removed line can only pair with the sixth or later. That is deliberate: a crossing match would let the listing print your new lines out of the order they appear in the file, and there are no line numbers here to catch that with. So when the real edit crosses, it pairs what it can in order and leaves the rest as a plain removal and a plain addition. Three settings reordered and all three edited:
+ DEBUG = False
- HOST = "0.0.0.0"
+ HOST = "127.0.0.1"
- PORT = 8080
+ PORT = 9090
- DEBUG = True
Two of the three matched and marked. Both DEBUG lines are present and both correct, just at opposite ends of the block instead of next to each other, which is where Line mode puts them too. Less helpful than I would like, and nothing on screen claiming something that did not happen, which is the bar the old version could not clear.
Accents, emoji and Japanese in Character mode
Character mode splits on code points, which is not the same thing as what looks like one character on your screen, and that gap shows up in three places worth knowing about.
An accent written as its own mark counts as its own character. The word café typed with a ready-made é is four code points. The same word typed as a plain e followed by a combining acute is five. They look identical, they are not equal, and Character mode marks the é on both sides. Delete only the accent and the highlight lands on one invisible mark that draws itself on top of the letter before it.
Emoji come apart according to how they were built. I counted the pieces Character mode sees:
| emoji | code points |
|---|---|
| thumbs up | 1 |
| thumbs up with a skin tone | 2 |
| flag of Japan | 2 |
| woman technologist | 3 |
| family of four | 7 |
Simple ones survive whole. Change only the skin tone on a thumbs up and the diff marks one code point removed and one added, which renders as two bare color swatches with no hand attached. Drop the boy from a family of four and it removes two pieces, a joiner and the boy.
Japanese, Chinese and Thai have no spaces between words, so Word mode reads a whole clause as one enormous word and paints the entire sentence red and green. On an eight character Japanese sentence with a three character edit, Word mode produced two pieces, meaning the lot, and Character mode produced four and isolated the three characters that actually changed. For any language written without spaces, use Character.
One last thing, from the Ignore case box. Lowercasing folds some characters together that look nothing alike. The Kelvin sign lowercases to a plain k, so with Ignore case on, a temperature written with the Kelvin sign compares equal to one written with a capital K and you will never see the difference. It cuts the other way too, the dotted capital I used in Turkish lowercases to an i plus a separate dot, so it still does not match a plain i even with the box ticked.
When this beats git or your editor
Git diffs what git tracks and your editor diffs the file you have open. Neither one helps the moment you are holding two loose blobs with no shared history, a config a coworker pasted into chat, output from two servers that are meant to agree, a contract that was never in a repo. Paste both sides, read the diff, move on. No stash, no throwaway commit just to see two versions next to each other.
Where git wins, and I will say it plainly: git diff --color-moved genuinely detects moved code and paints it as a move, and git gives you line numbers and a patch you can apply. You get neither of those from this page. If you need something git apply will take, generate it with git.
What you get instead is privacy on files that were never in a repo to begin with. The whole comparison runs inside your own browser tab. I drove every control on the page while watching browser storage, and it wrote 0 entries and read 0. The share link carries your view settings only, with Character mode and both toggles on it is exactly ?mode=char&ic=1&iw=1 and nothing else, so a link about a private file is safe to send. The comparison ships inside the page itself, so nothing more gets fetched when you press a button, which means once the page has loaded you can drop the connection and it keeps working.
Frequently asked questions
What do the numbers in the bar actually count?
Rows in the listing, not lines in your files, and they change with the highlight level. I took a four line file, edited one line and added one at the end. Line mode reported +2 added and -1 removed. Word and Character both reported +1 added and ~1 changed, because they merged the edited pair into a single changed row. The edit was identical both times and the counts were not. The amber ~ changed number only appears in Word and Character mode, since Line mode never produces a changed row.
Can I compare two files instead of pasting?
Drop a file onto either box, or click the load link under it to pick one off disk. It reads the text into the box and compares it right there, the file itself is never uploaded. The picker on each box offers twelve text extensions by name, .txt, .md, .json, .csv, .log, .yaml, .yml, .xml, .html, .css, .js and .ts, plus any other file the system reports as text, so an extension that is not on that list will still show up.
What does Copy diff actually give me?
A readable change list, two spaces in front of unchanged lines, - in front of removals and + in front of additions. Three lines in, two out, it looks like this:
keep
- old value
+ new value
- gone
No line numbers and no hunk headers, so it is something a reviewer can read in a ticket rather than something a patch tool can apply.
Will reordering keys in a JSON file confuse it?
It will show up as a move, because this compares lines in order and has no idea your file is JSON. I moved "name" from the top of a small object to the bottom and got the name line removed at the top and added at the bottom, plus one extra changed line, because moving the last key meant a trailing comma had to move too. So one reorder became a move plus an edit. If you only care about values, sort both sides first, or use a dedicated JSON comparison.
Does refreshing the tab lose what I pasted?
Yes, and there is nothing to recover it from. I watched browser storage across a full session of pasting, switching modes, ticking both boxes and swapping sides, and the count stayed at 0 writes and 0 reads throughout. There is no autosave and no history to bring anything back from. If you need to step away mid comparison, copy both sides into a file first.
Both boxes have the same text and the buttons went gray. Is that a fault?
No, that is the identical case. The bar says "Files are identical." and Copy diff and Download .diff both disable themselves, because an empty diff is not worth putting on your clipboard. The listing still renders every line in neutral so you can scroll it and satisfy yourself that both sides really did arrive.