Text Diff — Compare Two Blocks of Text Line by Line

Diff two blocks of text line by line: an exact longest-common-subsequence edit script listing added, removed and unchanged lines, a similarity percentage, and optional trailing-whitespace and case ignoring. Everything runs in your browser and what you paste never leaves this device.

How a line is judged

Where exactly the two differ

Two versions of a paragraph, a config file, a list of steps: the interesting question is never whether they differ — they do, or you would not be here — but which lines moved. Reading them side by side is unreliable; the eye slides over a changed article, a dropped comma, one missing item in a twelve-line block.

Paste both texts: an exact longest-common-subsequence computation matches them line by line, then replays the result as an edit script: unchanged, removed, added, in the order they appear. Four tiles summarise it, and the table carries a line number for each side so you can find the spot in your editor afterwards.

There is no word-level highlighting and no understanding of syntax. That is deliberate: the line is the unit, and within that unit the answer is provably the best alignment available.

How a line is judged

  • CRLF and a lone CR are turned into LF first, then the text splits on LF. No other character is rewritten, and the Content column shows the line exactly as you pasted it.
  • Two lines match only when their keys are equal. The key is the whole line by default; Ignore trailing whitespace strips trailing spaces and tabs before comparing, Ignore case lowercases it. Normalising changes the verdict only, never what is displayed.
  • The alignment is textbook dynamic programming: the cell at (i, j) records how many lines still match once the original starts at line i and the modified text at line j. The grid is filled bottom-up, then a walk from the top-left emits the script. Both sides are capped at 1200 lines, so at worst 1201 × 1201 cells are computed — an exact solution, no heuristics, no guessing.
  • An edited line shows up as two rows: that line removed, the new one added. Lines the LCS could not pair are exactly the ones that changed, so a classic edit script has no separate “modified” state.
  • Similarity = 2 × |LCS| ÷ (original lines + modified lines) × 100, shown to one decimal. Two identical ten-line texts give 100.0%. A 100-line original against a single surviving line gives 2 × 1 ÷ 101 ≈ 2.0%. Read it as a length-weighted overlap of matched lines, not as character-level sameness.
  • Line numbers are 1-based and per side. A removed row has no number in the Line (new) column and an added row none in Line (old); the gap is an em dash.
  • Untick Show unchanged lines and only additions and removals are drawn — the note states how many unchanged rows were folded away. Rendering stops at 3000 rows and says so out loud; the four counts always come from the full texts.
  • A trailing newline yields a final empty line, and the split keeps it instead of tidying it away. Empty lines appear as blank content cells but still occupy a line number.

Where it can mislead you

  • Lines, not words. One changed article in a paragraph of prose costs a whole removed row plus a whole added row. Diffing prose word by word needs different machinery.
  • A moved block looks like deletion plus insertion. LCS maximises matched lines; it has no idea that you simply relocated the paragraph. When added and removed counts are equal and small, check whether the text was reordered rather than rewritten.
  • Similarity rewards big files. Three extra lines in a 1000-line text still prints about 99.9%, and those three lines might be the whole point. Use it as an overview, never as a score.
  • The ignore options manufacture false matches. With Ignore case on, userId and userid are the same line — a real difference in code or in URLs. Ignore trailing whitespace only covers the end of a line, so indentation and tab-versus-space are still reported as changes.
  • Structure is invisible here. Reordered JSON keys, rewrapped markdown, a comment shuffled between statements: all appear as ordinary line edits.
  • 1200 lines is a wall. Past it you get a refusal rather than a truncated diff that looks complete.

Questions

Why does fixing one word take two rows? Because the output is an edit script: first the longest common subsequence of lines is found, then whatever failed to pair up is reported as removed on the left and added on the right. Only genuinely paired lines are called unchanged, which is what keeps the three counts and the similarity figure consistent with each other.

Does 100.0% similarity prove the texts are identical? Only when both ignore boxes are off. Otherwise it means “every line matched under the chosen tolerance” — turn Show unchanged lines on and look, or untick the options and compare again.

My line counts feel one too high. That is almost certainly the trailing newline: a\nb\n is three lines here (the last one empty), because splitting on LF keeps the fragment after the final break. Both sides are treated the same way, so the comparison stays fair even though the counts surprise people.

Where does my text go? Splitting, dynamic programming and rendering all happen inside this script. No request is made with your input, so what you paste never leaves this device.

Support on PayPal