Quick Answer
A text diff tool compares two blocks of text and highlights exactly what changed: additions in green, deletions in red, and unchanged text left plain. To use one, paste your original into the first box and the revised version into the second, and the differences appear instantly. It's the fastest way to spot every edit between two versions.
A text diff tool compares two versions of text and instantly shows you what changed between them, with additions and deletions highlighted so nothing slips past you. You paste the original into one box and the revised version into another, and the tool marks every edit. That's it. It turns the tedious job of eyeballing two nearly identical documents into a two-second glance. This guide covers how to use one, how the clever algorithm behind it actually works, who relies on these tools, the comparison modes, and the mistakes worth dodging.
Follow along with our free Text Diff tool. Paste two versions, see the differences light up instantly. It runs entirely in your browser, so your text never leaves your device.
Open the Text Diff Tool →What Is a Text Diff Tool?
A text diff tool, sometimes called a diff checker or text comparison tool, finds the difference between two pieces of text. The word "diff" is short for difference, and the concept comes straight from the Unix diff command that programmers have used since the 1970s to compare files.
The output is the useful part. Instead of just telling you the two texts are different, a diff tool shows you exactly where and how. Added text is typically marked in green, removed text in red, and anything unchanged is left alone. Some tools show the two versions side by side, others stack the changes inline. Either way, you see the edits at a glance rather than reading both versions in full and hoping to catch the one word that moved.
How Do You Use a Text Diff Tool?
Using one takes three steps and no technical skill at all:
- Paste your original text. Drop the first version into the left or top box. This is your "before".
- Paste the changed text. Put the revised version into the second box. This is your "after".
- Read the highlights. The tool compares them and marks what was added, removed, or kept. Scan the colored sections to see every change.
From there you can usually switch the comparison mode (more on that below) or toggle whether to ignore whitespace, which is handy when only the spacing changed and you don't care. The best part is speed. A diff that would take minutes to find by hand shows up the instant you paste. Pair it with our remove duplicate lines tool or word counter when you're cleaning up text.
Paste two versions of anything, an email draft, a paragraph, a config file, into the free Text Diff tool and watch the changes appear. No signup, nothing sent to a server.
Open the Text Diff Tool →How Does a Diff Tool Actually Work?
Under the hood, a diff tool is solving a genuinely elegant computer science problem: what's the smallest set of edits that turns text A into text B? It doesn't just compare line one to line one. It finds the longest sequence of content the two texts share, then treats everything else as additions or deletions.
That framing goes back to the original Unix tool. In "An Algorithm for Differential File Comparison", Bell Laboratories Computing Science Technical Report #41, dated July 1976, J. W. Hunt of Stanford and M. D. McIlroy of Bell Labs wrote that "the central algorithm of diff solves the 'longest common subsequence problem' to find the lines that do not change between files." They reported something that still holds up fifty years later: on real data, time and space usage was observed to grow roughly as the sum of the two file lengths, even though the worst case grows as the product. Diff was fast in practice because real edits are small.
The method most tools use today comes from a 1986 paper by Eugene W. Myers, then at the University of Arizona, "An O(ND) Difference Algorithm and Its Variations", published in Algorithmica, volume 1, pages 251 to 266. Myers showed that finding a longest common subsequence and finding a shortest edit script are equivalent to finding a shortest path across an "edit graph," then built a greedy algorithm running in O(ND) time and space, where N is the combined length of the two texts and D is the size of the smallest edit script. He also gave a refinement needing only O(N) space and, using suffix trees, a variation running in O(N log N + D²) time. In his own words, the algorithm "performs well when differences are small (sequences are similar) and is consequently fast in typical applications," which is exactly the case when you're comparing two drafts of the same paragraph.
That paper is not a museum piece. Git's official git diff documentation still lists myers as "the basic greedy diff algorithm" and notes that "currently, this is the default." Nearly forty years on, the thing highlighting your pull request is Myers. For the wider role of diffing inside version control, Petr Baudis surveys the field in "Current Concepts in Version Control Systems".
How Do You Read a Diff Output?
Every diff view uses the same three signals, so learning them once covers all of them:
- Green, or a leading
+, means added. This content is in the second version and wasn't in the first. - Red, or a leading
-, means removed. It was in the original and is gone from the revision. - Plain text means unchanged. It's shown as context so you can see where each change sits.
The original diff was terser about it. Hunt and McIlroy's report describes only three operations, append, change and delete, abbreviated to a, c and d, with lines from the first file flagged < and lines from the second flagged >. A line reading 3,4c4,6 means lines 3 to 4 of the original became lines 4 to 6 of the revision. That shape survives in the unified format Git prints, where a hunk header like @@ -3,4 +4,6 @@ carries the same information in a different order.
Browser tools drop the notation and color the text instead, which is why non-programmers can pick them up in seconds. The logic underneath is identical. And if a change looks bigger than you expected, it's usually the mode or the whitespace setting rather than the text.
That swap has a cost most tools never mention. The old notation carried the meaning in characters you could read: a plus, a minus, an angle bracket. Color-only output throws that away and leaves one signal doing all the work, and it happens to be the signal a lot of people can't read reliably. Red and green is the single worst pairing you could pick for it. Colour Blind Awareness puts red-green color vision deficiency at around 8 percent of men and 0.5 percent of women, rising to roughly 10 or 11 percent of men in Scandinavia.
There's a standard covering exactly this. W3C success criterion WCAG 1.4.1, Use of Color, sits at Level A, the baseline tier, and requires that "Color is not used as the only visual means of conveying information, indicating an action, prompting a response, or distinguishing a visual element." A diff that says added and removed purely through red and green fails that test.
So when you're picking a diff tool, look for one that keeps a second signal alongside the color. Plus and minus markers in the gutter, strikethrough on deleted text, or a separate added and removed column all work. If you're stuck with a color-only tool and the output is hard to read, switching it to line mode usually helps, because whole changed lines are easier to spot by position than a few tinted words mid-paragraph. And if you're sending a diff to someone else, say what changed in words too. Don't make the color do it alone.
Who Uses Text Diff Tools and Why?
Far more people than you'd guess. The obvious crowd is developers, but writers, editors, students, and legal and admin staff all lean on diffs.
- Developers compare versions of code and config files to see exactly what a change touched before they ship it.
- Writers and editors check what a reviewer altered in a draft, or compare two versions of an article without a full track-changes setup.
- Students and researchers compare drafts of an essay, or check their work against a source to be sure it isn't too close.
- Legal and admin teams spot every change between two versions of a contract or policy, where a single altered word can matter enormously.
Diffing is woven deepest into software work, and the numbers there are large. The Stack Overflow 2024 Developer Survey gathered responses from 65,437 developers across 185 countries, and the daily workflow that population shares is built on Git, which shows you a diff every time you stage a change or open a pull request. That's why the red-and-green change view has become a shared visual language: millions of people read one every working day, so a standalone diff tool feels familiar the first time you open it. For more of the everyday tools that fit this workflow, see our guide to free online developer tools.
What Are the Different Comparison Modes?
Not every diff should be measured the same way. Most tools let you choose how finely to split the text before comparing, and picking the right mode makes the result far easier to read.
| Mode | Compares by | Best for |
|---|---|---|
| Line | Whole lines at a time | Code, config files, structured data |
| Word | Individual words | Prose, essays, editing drafts |
| Character | Single characters | Spotting a typo or a changed digit |
Line mode is the classic and what Git shows you by default. Word mode is the friendliest for regular writing, because it points at the exact words that changed instead of flagging a whole line. Character mode is the most precise but can look busy, so save it for when you're hunting a tiny change, like a single wrong number in a long string.
Git can do word-level comparison too. Its documentation describes --word-diff, which by default delimits words by whitespace and shows removals as [-removed-] and additions as {+added+}. Useful when a prose file has been rewrapped and line mode turns the whole paragraph red.
Which Diff Algorithm Should You Use?
In a browser tool you usually don't choose, and you don't need to. In Git you can, and it changes how readable a messy diff looks. The official documentation for --diff-algorithm lists four options:
| Algorithm | What the Git docs say | When it helps |
|---|---|---|
| myers | "The basic greedy diff algorithm. Currently, this is the default." | Almost everything. Leave it alone unless a diff reads badly. |
| minimal | "Spend extra time to make sure the smallest possible diff is produced." | Small files where you want the tightest possible result. |
| patience | "Use 'patience diff' algorithm when generating patches." | Refactors where blocks moved and the default output looks scrambled. |
| histogram | "This algorithm extends the patience algorithm to 'support low-occurrence common elements'." | Code full of repeated lines, like closing braces or blank lines. |
The "when it helps" column above is the conventional advice, and there's research testing whether it holds. Nugroho, Hata and Matsumoto measured it directly in "How Different Are Different diff Algorithms in Git?", published in Empirical Software Engineering in 2020. Two numbers show how much the choice actually moves. Across 14 Java projects, code churn metrics came out different in 1.7 to 8.2 percent of commits depending purely on which algorithm ran. And across 10 Java projects, between 6.0 and 13.3 percent of identified bug-fix commits pointed at a different bug-introducing change. Same repository, same commits, different answer.
Their conclusion goes further than the Git docs do. On manual analysis of how well each algorithm represented the actual code change, Histogram beat the Myers default, and the authors "strongly recommend using the Histogram algorithm when mining Git repositories." So histogram isn't only for files full of repeated lines, as the table suggests. If you're analysing history rather than just reading a diff, it's arguably the better default.
Two caveats before you change anything. That research is about mining repositories at scale, not about glancing at a pull request, so for everyday reading the default is fine. And none of it applies to a browser diff tool, which makes the choice for you.
The practical takeaway for everyone else: if a diff looks noisier than the edit deserves, the algorithm is not usually the culprit. Whitespace is. Git's -w flag, documented as --ignore-all-space, will "ignore whitespace when comparing lines," and most browser tools have the same switch under a plainer name.
Why Does a Diff Tool Slow Down or Give Up?
Because the cost isn't really about how big your files are. It's about how different they are, and that surprises almost everyone.
The default algorithm goes back to Eugene Myers at the University of Arizona, whose 1986 paper An O(ND) Difference Algorithm and Its Variations is still what most tools implement. The complexity in that title is the whole story. Myers describes "a simple O(ND) time and space algorithm," where, in his words, "N is the sum of the lengths of A and B and D is the size of the minimum edit script for A and B."
Read those two variables again. N is total size. D is how much actually changed. The runtime depends on both multiplied together, which means the number of edits between your two texts matters just as much as their length.
Myers says as much directly: the algorithm "performs well when differences are small (sequences are similar) and is consequently fast in typical applications." Typical applications being the normal case, where you changed a few lines. He also gives an expected-time result of O(N + D squared) under a basic stochastic model, and notes that a refinement needs only O(N) space.
So here is the practical shape of it, and it explains behaviour that otherwise looks random:
- Two large files that are nearly identical diff quickly. D is tiny, so the work stays small no matter how long the files are. This is the everyday case and it's why diffing feels instant.
- Two medium files with almost nothing in common can crawl. D approaches N, and the cost climbs steeply. Pasting two completely unrelated documents is the worst thing you can hand a diff tool, and it's a thing people do by accident constantly.
- A reformatted file behaves like an unrelated one. Reflow the whole thing, or change every line ending, and you have technically edited every line. D goes through the roof even though nothing meaningful changed. That's the same root cause as the line endings problem further down, viewed from the performance side.
Which points at the fix. If a diff hangs or truncates, the instinct is to blame the file size and split it up. Usually the better move is to reduce D instead: turn on ignore whitespace, normalize line endings, switch from character mode to line mode, and see whether the two texts were only superficially different all along.
And if they genuinely are unrelated documents, a diff was never going to tell you much. A tool showing you that everything was deleted and everything was added is technically correct and completely useless, which is a good sign you want a different comparison entirely.
Why Does a Moved Paragraph Show Up as Deleted and Added?
Because to the algorithm, that's exactly what happened. This is the single most confusing thing about reading a diff, and it catches people out constantly: you move a paragraph from the top of a document to the bottom, change not one word of it, and the diff lights up like you rewrote the whole thing. Red where it used to be, green where it now is, twice the noise for zero actual editing.
It isn't a bug, and it isn't your tool being dim. Go back to how diff is defined. Both the Hunt and McIlroy report and the Myers paper frame the job as finding a shortest edit script built from insertions and deletions, on the way to a longest common subsequence. A subsequence has to keep its original order. So a block that jumps from line 5 to line 400 can't stay in the common subsequence, because it's out of sequence by definition. The only vocabulary the model has for "this text is now somewhere else" is delete it from here, insert it over there. There is no move operation to reach for. Nearly every diff you've ever read inherits that limitation.
Which means the fix isn't a better algorithm. It's a second pass that spots the pattern after the diff is done and colors it differently.
Git ships exactly that. The git diff documentation describes --color-moved as making "moved lines of code" get "colored differently," and it's off unless you ask for it: the mode "defaults to no if the option is not given and to zebra if the option with no mode is given." Turn it on and moved text stops shouting at you. The modes are worth knowing because they behave quite differently:
| Mode | What the Git docs say | When to reach for it |
|---|---|---|
| plain | "Any line that is added in one location and was removed in another location will be colored." But it "is not very useful in a review to determine if a block of code was moved without permutation." | Rarely. It catches single lines but can't tell you whether a block stayed intact. |
| blocks | "Blocks of moved text of at least 20 alphanumeric characters are detected greedily." Note that "adjacent blocks cannot be told apart." | One block moved. Simple and readable. |
| zebra | Detects blocks as in blocks, then alternates two colors, where "the change between the two colors indicates that a new block was detected." | The default, and the right pick when several blocks moved at once. |
| dimmed-zebra | "Similar to zebra, but additional dimming of uninteresting parts of moved code is performed." Only "the bordering lines of two adjacent blocks are considered interesting." | Large moves where you only care about the seams. |
That 20-character floor in blocks mode is worth holding on to. Move a short line and Git won't count it as a move, so a stray closing brace or a one-word heading still reads as a plain delete and add. Not a malfunction, just the threshold doing its job.
There's a second trap, and it's the one that makes people give up on the feature. Move a block and re-indent it, which is what happens whenever code shifts into or out of a loop, and move detection can miss it entirely, because the lines aren't byte-identical any more. Git has a separate switch for that. --color-moved-ws "configures how whitespace is ignored when performing the move detection," and its allow-indentation-change mode is built for this exact case: it will "initially ignore any whitespace in the move detection, then group the moved code blocks only into a block if the change in whitespace is the same per line." So a block that shifted one tab deeper as a unit still registers as one move. The docs flag that this mode "is incompatible with the other modes," so pick it rather than stacking it.
Now the part that matters if you're using a browser diff tool rather than Git. Most of them don't do move detection at all. You'll get the honest delete-and-add view, and no amount of switching between line, word and character mode will change it, because the mode controls how the text is split, not whether reordering is recognised. Three ways to work around it:
- Diff the moved block on its own. Paste just the old copy and just the new copy into a fresh comparison. If it comes back clean, the text genuinely moved unchanged and you can stop worrying about that whole red-and-green region.
- Compare sorted versions when order doesn't matter. For lists, config keys, imports or CSV rows, sort both sides first and diff that. What's left is real additions and removals, with pure reordering stripped out.
- Check the totals. If the same number of lines went red and green and the counts match, a move is the likeliest explanation. It's a rough signal, not proof, but it tells you where to look first.
And the reason this is worth the effort rather than something to shrug at: a move that reads as a full rewrite is where real changes hide. When 60 lines are already highlighted, the one word someone quietly edited inside the moved block sits in the middle of all that color looking exactly like the rest of it. That's the failure mode to guard against, whether you're reviewing a pull request or a contract.
Can a Diff Tool Catch Invisible Characters?
Yes, and this is the job a diff tool does that nothing else does as well. It's also the reason to reach for one when two texts look identical but something is clearly wrong.
Some characters are impossible to tell apart by eye. The Latin letter a and the Cyrillic а at code point U+0430 render the same in most fonts. So do Latin o and Greek omicron ο at U+03BF. The Unicode Consortium catalogues these in Unicode Technical Standard 39, Unicode Security Mechanisms, which ships a confusables.txt data file mapping thousands of characters to their visual equivalents specifically so software can detect when two strings are confusable. UTS 39 exists because this is a security problem: a username or domain built from a swapped-in lookalike passes a human read and impersonates the real thing.
Then there's the genuinely invisible category. Zero-width spaces, non-breaking spaces where a normal space should be, and smart quotes that arrived when someone pasted from a word processor. None of them show up as anything at all, and all of them break string comparisons, config parsing, and code.
A diff tool is how you find them, because it compares code points rather than shapes. Two things make it work:
- Switch to character mode. Word and line mode will flag the whole word or line as changed without telling you which character did it. Character mode narrows it to the exact position.
- Trust the tool over your eyes. If a diff insists two apparently identical strings differ, they differ. That's the tool doing its job, not a glitch. The characters are different even though the pixels aren't.
Worth knowing when a password that's definitely correct keeps failing, when a CSV column refuses to match, or when a config value that reads perfectly won't parse. Paste the working version and the broken one into a diff, set it to character mode, and the culprit usually lights up in one keystroke.
The flip side is worth stating too. A diff compares text, not meaning. Reformatted JSON, reordered CSS properties, and re-wrapped prose can diff heavily while being functionally identical, and a single flipped digit or boolean can diff almost invisibly while changing everything. The tool tells you what changed. Deciding whether it matters is still your job.
Why Do Some Diffs Flag Text That Is Genuinely the Same?
The section above told you to trust the tool over your eyes. That holds for lookalike characters. But there's a second category where it's exactly the wrong instinct, and it's the one that wastes the most time, because here the tool is flagging a difference that Unicode itself says isn't one.
Take the letter é. It can be stored two ways: as a single code point, U+00E9, or as a plain e followed by a combining acute accent at U+0301. Same letter, same meaning, same pixels on screen. Different bytes underneath. A diff comparing code points marks it as changed, and character mode will point at a position where you can see nothing wrong at all.
Unicode has a name for this. Unicode Standard Annex #15, Unicode Normalization Forms, defines canonical equivalence as "a fundamental equivalency between characters or sequences of characters which represent the same abstract character, and which when correctly displayed should always have the same visual appearance and behavior." Read that carefully. Not similar characters. The same abstract character, written down two different ways.
The fix is normalization, which rewrites text into one consistent form so equivalent sequences end up byte-identical. UAX #15 defines four of them:
| Form | What UAX #15 calls it | In practice |
|---|---|---|
| NFC | "Canonical Decomposition, followed by Canonical Composition" | The usual choice. Pulls text toward single precomposed characters. |
| NFD | "Canonical Decomposition" | Splits accented letters into base plus combining marks. |
| NFKC | "Compatibility Decomposition, followed by Canonical Composition" | Also folds formatting variants. Lossy, so use it deliberately. |
| NFKD | "Compatibility Decomposition" | Same caveat, decomposed. |
NFC is the one you want most of the time. The standard states its design goal plainly: if two strings x and y are canonical equivalents, then toNFC(x) = toNFC(y). Normalize both sides to NFC before comparing and the phantom difference just evaporates.
So where does mismatched text come from in the first place? Often from moving between systems. Apple documents this directly in its Apple File System Guide: "HFS+ stores the normalized form of the filename on disk to provide normalization insensitivity," while "APFS preserves the normalization of the filename and uses hashes of the normalized form of the filename to provide normalization insensitivity." The two file systems don't even agree on which Unicode they're speaking. Apple notes that APFS "implements normalization and case insensitivity according to the Unicode 9.0 standard," compared with HFS+, "which is based on Unicode 3.2." Six major Unicode versions apart, on the same machine, across an OS upgrade. Apple's own warning is that "attempting to create a file using one normalization behavior and then opening that file using another normalization behavior may result in ENOENT, or 'File Not Found' errors."
And here's the part people most often get wrong, because it looks like the same problem as the previous section but needs the opposite fix. Normalization does not solve confusables, and confusable detection does not solve normalization. The W3C makes the point with a worked example in Character Model for the World Wide Web: String Matching, lining up Greek capital rho at U+03A1, Cyrillic capital er at U+0420, and Latin capital P at U+0050. Three characters, one shape. The document's verdict is that "Unicode Normalization will not fold these characters together," because they aren't canonically equivalent. They're genuinely different letters that happen to look alike.
Which gives you a clean way to sort out any diff that flags text you're sure is identical:
- If normalizing both sides to NFC makes the difference vanish, it was canonical equivalence. Nothing was ever really edited, and the sensible fix is to normalize on the way in rather than to hunt through the text.
- If the difference survives normalization, you're looking at genuinely different characters. That's the confusables or invisible-character case from the previous section, and now it's worth hunting.
Most browser diff tools don't normalize for you, which is the right default. A tool that quietly normalized your text could hide a real change you needed to see. But it does mean the first question to ask when a diff insists two identical-looking strings differ is which of these two things you're actually dealing with.
Why Does Diffing JSON or XML Give Such Noisy Results?
Because a text diff compares characters, and structured formats let identical data be written a dozen different ways. The diff is telling you the truth about the bytes while completely misleading you about the meaning.
This is the same problem as the section above, moved up a layer. Unicode normalization is about two strings that are the same character written differently. This is about two documents that are the same data written differently.
JSON is the common case. The IETF specification, RFC 8259, notes that "JSON parsing libraries have been observed to differ as to whether or not they make the ordering of object members visible to calling software", and says implementations whose behaviour doesn't depend on member order interoperate better. Which tells you what you need to know. Reorder the keys in an object and most consumers see the same data. A text diff sees a rewritten file.
XML is bad enough that the W3C wrote a whole specification about it. Canonical XML 1.1 opens on the observation that "any XML document is part of a set of XML documents that are logically equivalent within an application context, but which vary in physical representation", and exists so that "if two documents have the same canonical form, then the two documents are logically equivalent". When the standards body needs a canonicalization spec to answer are these the same, a byte-comparison tool was never going to manage it on its own.
Day to day, three things bite:
- Reformatting changes everything. Two-space to four-space indentation, or minified to pretty-printed, and every single line is different. Nothing about the data changed.
- Reordered keys read as delete plus add. Scattered across the file, exactly the pattern the moved-block section above describes, and just as hard to read.
- The real change hides in the noise. This is the dangerous one. Someone reformats a config file and edits one value in the same commit. The value change is in there, sitting in a wall of red and green that all looks the same.
The fix is unglamorous and it works. Canonicalize both sides before you compare. Run both through the same formatter with the same settings, sort the keys if your tool offers it, then diff. Most of the noise disappears and what's left is usually the actual change. Our JSON formatter does the formatting half, and the JSON formatter guide covers the settings that matter.
And if you're doing this often, stop using a text diff for it. Structure-aware diff tools exist for JSON, XML and CSV, and they compare the data rather than the characters. A text diff is the wrong instrument the moment a format has its own rules about what counts as the same.
What Happens When You Diff a Word File or a PDF?
You compare a shadow of it. A diff tool takes text, and a Word document or a PDF is not text, so the moment you select all and copy, most of what would have told you what changed has already been left behind. For the contract review this article mentions earlier, that matters more than any algorithm choice.
Start with what a .docx actually is, because the answer surprises people who have used Word for twenty years.
It's a ZIP file. The format is ECMA-376, also published as ISO/IEC 29500, and it comes in four parts, one of which is called Open Packaging Conventions. Ecma's own overview of the standard puts it plainly: "Every Office Open XML file comprises a collection of byte streams called parts, combined into a container called a package", with a physical implementation that uses the Zip file format, so that "the ZIP archive is one package, each ZIP item in the archive is one part".
Now think about what copying out of Word does to that. You are reaching into one part of a package, pulling out the rendered text, and discarding every other part. Then you paste the result into a diff tool and ask it whether two documents are the same.
Here's what didn't come with you.
- Tracked changes, and who made them. Revisions live as markup, not as visible text. Copy a document with changes accepted in view and you get the clean text, with no trace that anything was ever proposed, by whom, or when.
- Comments. Same story. They sit in their own part and copy as nothing.
- Styles and numbering. A defined term that stopped being bold, or a clause that renumbered because one above it was deleted, produces identical pasted text.
- Headers, footers and footnotes. Not part of the body text run, so usually not part of your selection either.
- Fields and cross-references. You copy what they currently display, not what they point at. A reference that now points somewhere else can paste the same string.
Which produces the failure mode worth naming out loud, because it's the dangerous direction. A paste-based diff can show no difference between two documents that genuinely differ. Every other problem in this guide makes a diff noisier than it should be. This one makes it quieter, and a quiet diff is the one people act on.
PDFs have their own version of the problem, from the opposite end.
A PDF doesn't store paragraphs. It stores instructions for painting glyphs at positions on a page, and what you get when you copy is whatever the text extractor managed to reconstruct from that. Which is why pasted PDF text arrives with line breaks at the visual line ends rather than at the paragraph ends, why two columns sometimes interleave, and why hyphenated words split across lines come out split. None of that was in the author's text. It's an artifact of extraction.
So a PDF-to-PDF comparison by pasting is not comparing two documents. It's comparing two reconstructions, and a difference you spot might be a change or might be the extractor having a different day. Run the line-ending and whitespace advice from earlier in this guide before you trust any of it.
You might reasonably ask why not just unzip the .docx and diff the XML, since it's XML underneath. You can. It will be close to unreadable, and the section above on structured data explains exactly why: the same logical document has many valid physical representations, attribute order and whitespace shift freely, and a byte comparison flags all of it. Word rewrites far more of that file on save than you changed.
What to do instead, in rough order of how much you should trust it:
- Use the application's own compare. Word's Compare feature works on the document model rather than on extracted text, so it sees renumbering, style changes and revision history. For a contract, that is the tool, and a browser diff is the sanity check afterwards.
- If you must paste, paste both sides identically. Same application, same paste mode, same selection method. Extraction artifacts that appear on both sides cancel out. Mixing a paste-as-plain-text on one side with a rich paste on the other guarantees noise.
- Normalize before you compare. Line endings and whitespace first, then Unicode normalization, both covered above. On pasted document text these matter more than usual, not less.
- Treat a clean result as unproven, not as proof. If a paste diff shows nothing, the honest report is that the visible text matches. Say that sentence rather than "no changes", especially if somebody is relying on it.
- Go back to the source format when the stakes are real. If two PDFs came from two Word files, compare the Word files. A PDF is the output, and comparing outputs tells you less than comparing inputs.
The general rule underneath all of this is the one this guide keeps returning to. A diff tool answers exactly one question, which is whether these two strings differ. It's very good at that. What it cannot do is tell you whether the two things the strings came from are the same, and with formatted documents the gap between those two questions is at its widest.
Why Does Character-Level Diff Mangle Emoji and Accents?
Because what you call a character and what the diff calls a character aren't the same thing. Switch to character mode on text containing emoji or accented letters and the output can come back looking like nonsense. That isn't a bug in the tool so much as a mismatch in units.
The Unicode Consortium has a standard for this. UAX #29, Unicode Text Segmentation, "describes guidelines for determining default segmentation boundaries between certain significant text elements: grapheme clusters ("user-perceived characters"), words, and sentences." That phrase in quotes, user-perceived characters, is the whole idea. As the annex puts it, "there are many cases where such a basic unit is made up of multiple Unicode code points."
Two examples make it concrete.
- Accents built from parts. UAX #29 uses g with a diaeresis, which can be stored as U+0067 LATIN SMALL LETTER G followed by U+0308 COMBINING DIAERESIS. Two code points, one thing you'd point at on screen.
- Emoji joined together. Sequences glued with a Zero Width Joiner count as a single cluster under the annex's rule GB11, which forbids a break inside them. A family emoji or a profession emoji is often four or five code points wearing one face.
Now picture a character-level diff walking through that. Most implementations step through code points, not clusters. So it can cheerfully mark U+0308 as deleted and leave the g behind, or split a ZWJ sequence down the middle. You get a highlight covering half a character, a rendering that looks broken, and a change count that's wrong in a way you can't easily talk about.
Worse, the damage is invisible in the direction that matters. The two texts might differ by exactly one visible character, and the diff reports several changes because it counted code points. If you're using that number to decide whether an edit was minor, it's lying to you.
Three practical responses, in order of how often they're the right one.
- Use word mode instead. The simplest fix by far. Word boundaries sit outside grapheme clusters, so the problem disappears without you configuring anything. Character mode earns its place on short strings like IDs and hashes, which rarely contain emoji.
- Normalize first. This connects to the section above. The annex notes that "grapheme clusters remain unchanged across all canonically equivalent forms of the underlying text", so normalizing both sides removes the composed versus decomposed question and leaves you comparing like with like.
- Distrust character counts across tools. Different tools count different things, and none of them is wrong exactly. A count of code points, a count of UTF-16 units, and a count of grapheme clusters can give three answers for one emoji. If two tools disagree about length, that's usually why rather than a fault in either.
Where this bites hardest is content that mixes scripts, which is most real content now. Names with diacritics, Indic and Thai text where combining marks are routine rather than occasional, and anything with emoji in it. If your diff output looks scrambled on text that renders fine everywhere else, try word mode before you go hunting for a deeper problem. It's almost always this.
Why Does the Whole File Show as Changed When You Didn't Touch It?
Nine times out of ten, line endings. You open a file written on a Mac, save it on Windows, and every single line turns red and green even though you edited nothing. It's the most common false alarm in diffing, and it's invisible by definition.
The reason sits in how a line is defined. POSIX, IEEE Std 1003.1, defines a line at 3.206 as "a sequence of zero or more non-<newline> characters plus a terminating <newline> character." Read that closely. The terminator isn't a separator sitting between lines. It's part of the line itself. So when you change what that terminator is, you really have changed every line in the file, and the diff is telling you the plain truth.
Three conventions are in play. Unix and modern macOS end a line with a single line feed, LF, byte 0A. Windows uses a carriage return followed by a line feed, CRLF, bytes 0D 0A. Classic Mac OS used a bare CR, which still turns up in old files. Move a file between the first two and every line quietly gains or loses one byte you can't see.
Git has a switch for reading past it. The git diff documentation describes --ignore-cr-at-eol as "ignore carriage-return at the end of line when doing a comparison," which is narrower and safer than reaching for -w and switching off whitespace comparison altogether. But that only cleans up the view. The durable fix is to stop the mismatch happening, and Git does that through gitattributes. Marking a path with the text attribute "enables end-of-line conversion: When a matching file is added to the index, the file's line endings are normalized to LF in the index," and the docs stress this happens "every time the file is checked in, even if the file was previously added to Git with CRLF line endings." Set text=auto and Git "decides by itself whether the file is text or binary." Everyone commits LF, everyone checks out whatever suits their machine, and the phantom diffs stop.
Browser diff tools mostly don't give you that control, so two habits help. Character mode will show you a difference at the end of every line where you can see nothing, which is the signature. And if a tool offers an ignore-whitespace toggle, try it, though be aware that some implementations trim spaces and tabs without touching carriage returns at all.
The last line only, and nothing else. Different cause, same family. POSIX also defines an incomplete line, at 3.195, as "a sequence of one or more non-<newline> characters at the end of the file." A file whose final line has no terminating newline ends in one of those. Add or remove that final newline and the last line changes, because under the definition above the newline was part of it. This is why Git prints the marker \ No newline at end of file rather than silently ignoring it, and why an editor configured to add a trailing newline on save can produce a one-line diff on a file you only opened and closed.
The first line only, and nothing else. That's usually a byte order mark. The Unicode Consortium's FAQ describes a BOM as "the character code U+FEFF at the beginning of a data stream, where it can be used as a signature defining the byte order and encoding form." In UTF-8 it's three bytes, EF BB BF, sitting in front of everything and rendering as nothing at all. Save the same file from two different editors and one may add it while the other doesn't. Unicode is blunt about the trouble it causes: "some recipients of UTF-8 encoded data do not expect a BOM," and its presence "will interfere with any protocol or file format that expects specific ASCII characters at the beginning, such as the use of '#!' of at the beginning of Unix shell scripts." So a BOM can break a script and show up in your diff as a first line that looks identical to the one above it.
Put those together and you can diagnose most of these in seconds, just from the shape of the highlighting:
| What you see | Likely cause | What to do |
|---|---|---|
| Every line changed, no visible edit | CRLF against LF | Ignore carriage returns, then normalize the file properly |
| Only the final line changed | Trailing newline added or removed | Look for the no-newline marker; set your editor to be consistent |
| Only the first line changed | UTF-8 byte order mark | Save both files with the same BOM setting |
| Scattered line ends changed | Trailing spaces or tabs | Ignore whitespace at end of line, or strip it on save |
One thing worth keeping straight. This is a different problem from the Unicode normalization case above, even though both make a diff flag text you're certain you didn't touch. Normalization is about two byte sequences that Unicode considers the same character. Line endings and BOMs are genuinely different bytes doing an invisible job, and no amount of normalizing will fold them together. Ask which one you're looking at before you start hunting.
Why Can't a Diff Tool Just Merge for You?
Because it's missing a piece of information, and no amount of cleverness gets it back. This is worth understanding properly, because it's the difference between a tool that shows you a problem and one that can solve it.
A plain diff has two inputs. It can see that line 40 says one thing on the left and something else on the right. What it cannot possibly know is which side changed it. Maybe you edited line 40 and your colleague left it alone. Maybe they edited it and you didn't. Maybe you both did. The two texts look identical in all three cases, so picking a winner would be guessing.
Add a third input and the guessing stops. That's a three-way merge, and Git's documentation for git merge-file states the mechanic in one line: "Given three files current, base and other, git merge-file incorporates all changes that lead from base to other into current." The docs spell out the setup too: "Suppose base is the original, and both current and other are modifications of base, then git merge-file combines both changes."
With the original in hand, every line answers itself. Unchanged from base on your side means their edit wins. Unchanged on theirs means yours does. Changed on neither means nothing happens. Only one case is genuinely undecidable, and Git says exactly when: "A conflict occurs if both current and other have changes in a common segment of lines."
That's where the markers you've probably seen come from. Per the same docs, a conflict gets bracketed with lines containing <<<<<<< and >>>>>>>, with your version above the ======= and theirs below. Those markers aren't an error. They're the tool telling you it did everything it could and this one needs a human.
You can also tell it to stop asking. Git's --ours, --theirs, and --union options resolve conflicts automatically, favouring lines from current, lines from other, or lines from both respectively. Handy for generated files, and a good way to lose real work everywhere else.
Two practical things follow from all this:
- Keep the original when two people edit separately. If you send a document to two reviewers, save the version you sent. Comparing their two returned copies against each other is the unanswerable problem above. Comparing each against the original turns it into an answerable one, and you can see who changed what.
- A browser diff tool isn't broken for not merging. It has two boxes because it takes two inputs. Merging needs a third, and that's a different tool with a different job. Use the diff to find what moved, then decide what to do with it yourself.
None of which makes two-way diff the lesser tool. Most of the time you genuinely just want to know what's different, and everything else in this guide is about doing that well. But if you keep wishing the tool would pick for you, the fix isn't a better diff tool. It's holding on to the version both copies came from.
Can Someone Hide Code in a Diff You Approve?
Yes. The invisible-character section above covers characters that hide. This is the sharper version: characters that lie about the order everything else appears in, and there's a named attack built on exactly that.
Nicholas Boucher and Ross Anderson presented it at the 32nd USENIX Security Symposium as Trojan Source: Invisible Vulnerabilities. Their one-sentence description of the attack is all you need: "We present a new type of attack in which source code is maliciously encoded so that it appears different to a compiler and to the human eye."
Read that again with code review in mind. The reviewer approves what they see. The machine runs what's actually there.
The mechanism is a legitimate feature
It works through Unicode's bidirectional algorithm, which exists for entirely good reasons. As Unicode Annex 9 puts it, the algorithm handles "the positioning of characters in text containing characters flowing from right to left, such as Arabic or Hebrew."
To make mixed-direction text work, Unicode defines explicit directional formatting characters, and Annex 9 lists them. Embeddings: LRE at U+202A and RLE at U+202B, closed by PDF at U+202C. Overrides: LRO at U+202D and RLO at U+202E. Isolates: LRI at U+2066, RLI at U+2067, FSI at U+2068 and PDI at U+2069.
Nine characters. Every one of them invisible. Every one of them capable of changing the order in which the characters around it are displayed.
Drop the right one inside a comment and the comment can appear to end somewhere it doesn't. A line that reads as a harmless remark to a reviewer compiles as an instruction. Or a line that looks like live code turns out to be inert.
This isn't a niche language problem either. Boucher and Anderson demonstrated working examples in C, C++, C#, JavaScript, Java, Rust, Go, Python, SQL, Bash, Assembly and Solidity, and report that the vulnerabilities affect "most compilers, editors, and repositories."
Which makes your diff tool either the defence or another victim
Here's the part that matters for choosing one. A diff tool renders text, exactly like the editor that fooled the reviewer in the first place. If it renders bidi controls faithfully, it shows you the same lie. If it escapes them, flags them, or displays them as visible markers, it shows you the truth.
So the question to ask isn't whether a tool compares accurately. It's whether it renders or reveals.
- Test it once with a control character. Paste a line containing U+202E and see what the tool does. If your text silently reorders itself on screen, the tool is rendering. If you get an escape sequence or a warning marker, it's revealing.
- Use character-level mode when it matters. The modes section above covers the trade-offs. For code review of anything sensitive, the noisier mode is the safer one.
- Watch the lengths. Two lines that look identical but report different character counts contain something you cannot see. That's the same tell the invisible-character section describes, and it works here too.
- Keep two renderers in the loop. If your editor and your diff tool handle these characters differently, the disagreement between them is information. One of them is showing you something the other is hiding.
Two honest limits. The authors' own recommended fix is at compiler level, with editor, repository and pipeline controls as interim mitigations, so a diff tool is a mitigating control rather than a solution and they say as much. And if you only ever compare prose, you will probably never meet this. It matters when you're reviewing something that will be executed or parsed, which is where the stakes of a missed character stop being cosmetic.
It's the cleanest illustration of the point running through this whole guide. A diff tool works on bytes while you work on glyphs. Usually that gap is a curiosity. Here it's the entire security property.
How Do You Check a Diff Tool Really Runs in Your Browser?
This guide has told you more than once to use a tool that works in your browser so your text never leaves your machine. Fair advice. But notice the gap in it. Every diff tool on the web says exactly that on its homepage, and "check that the tool says it works locally" just means trusting the claim of whoever wants your traffic.
You don't have to trust it. You can check in about fifteen seconds, and the check works on any tool including this one.
Open the Network panel first. Press F12, click Network, then paste your text and run the comparison. Per the Chrome DevTools network reference, DevTools records all network requests so long as DevTools is open, and the Requests table logs each one with its name, status, type and size. If your text is being uploaded, a request appears at the moment you hit compare. Click it and read the payload.
Then run the test that actually settles it. In the throttling menu, switch to Offline, which the same documentation describes as simulating a completely offline network experience. DevTools puts a warning icon next to the Network tab so you know it's on. Now compare your two texts again. If the diff still works with the network switched off, the comparison is happening on your machine and there is nowhere else it could be happening. That's proof rather than a promise, and it's the one test worth remembering.
Two things that trip people up, though.
A page can look clean while you watch and still send on the way out. Browsers have a purpose-built method for this. MDN describes navigator.sendBeacon() as asynchronously sending an HTTP POST containing a small amount of data, transmitted when the user agent has an opportunity to do so, without delaying unload or the next navigation. It exists because sites want to report at the moment you leave, which is precisely when you've stopped looking. The fix is the Preserve log checkbox: tick it, close the tab or navigate away, then come back and read what fired.
Requests are not all the same thing. Almost every free tool loads analytics, fonts and ads, so seeing traffic doesn't mean your paragraphs went anywhere. What matters is the payload. An analytics ping carrying a page URL is ordinary. A POST whose body contains your contract text is not, and you can tell them apart in one click. Judge what's in the request, not how many there are.
One honest limit on all of this. You're testing the version of the page in front of you right now, and code can change tomorrow. For a one-off comparison that's plenty. For anything you'd be genuinely hurt to leak, an offline tool on your own machine removes the question rather than answering it, and some documents deserve that.
What Mistakes Should You Avoid?
Diff tools are simple, but a few habits trip people up:
- Using the wrong mode. Running a character diff on two long essays buries the real changes in noise. Match the mode to the content.
- Forgetting about whitespace. If a tool counts spaces and tabs, a file that only had its indentation reformatted can look completely rewritten. Turn on "ignore whitespace" when the spacing doesn't matter. And if every line is flagged, check the line endings before you check anything else.
- Pasting sensitive text into a server-based tool. For a contract or private code, use a tool that works in your browser so nothing is uploaded.
- Assuming diff means merge. A diff shows changes; it doesn't combine them. If you need to blend two versions, you want a merge tool, not a plain diff.
- Comparing texts that are formatted differently. One version with curly quotes and another with straight quotes will show differences that aren't really edits. Normalize the formatting first.
Get those right and a diff tool becomes one of those quiet utilities you reach for constantly. Keep the Text Diff tool in a tab, and check our free tools for developers for the rest of the kit.
What Else Do People Ask?
What is a text diff tool?
A text diff tool compares two blocks of text and highlights exactly what changed between them: what was added, what was removed, and what stayed the same. You paste an original and a revised version, and the tool marks the differences line by line or word by word. It saves you from squinting at two documents side by side trying to spot a single edit. Our free Text Diff tool runs entirely in your browser.
How do you compare two texts online?
Open a text diff tool, paste your original text into the first box and the changed version into the second, then let it highlight the differences. Additions usually show in green and deletions in red. You do not need to install anything or write code. Adjust the comparison mode to word, line, or character level depending on how fine-grained you want the result to be.
Is it safe to paste text into an online diff tool?
It depends on the tool. The safest ones do all the comparison in your browser, so your text is never uploaded to a server. That matters for anything private, like a contract draft or unreleased code. Check that the tool says it works locally, and for truly sensitive material, prefer a browser-based tool or an offline one over anything that sends your text away to process it.
What is the difference between word, line, and character diff?
They control how finely the tool splits the text before comparing. Line diff marks whole lines as changed and suits code and structured files. Word diff highlights the specific words that changed and reads best for prose and editing. Character diff goes down to individual letters, which is handy for spotting a single typo or a changed digit but can look noisy on longer text.
Can a diff tool merge changes?
Some can, but a basic diff tool only shows the differences. Merging means combining two versions into one, choosing which change to keep where there is a conflict. Full version control systems like Git include merge tools, and some online diff checkers offer a merge view. For simply seeing what changed between two texts, a plain diff tool is all you need.