Toolmingo
Guides7 min read

How to Diff Text Files (and What the Output Means)

Learn how text diffing works, how to read unified diff output, and how to compare text files in JavaScript, Python, Go, and PHP — plus an online diff tool.

Diffing is the process of comparing two pieces of text and showing exactly what changed between them. It's one of the most important tools in software development — git diff uses it, code review tools use it, and documentation systems use it. Understanding how diffs work makes you a better developer.

What a diff shows

A diff takes two inputs — usually called A (the original) and B (the modified version) — and produces output that marks every line as:

  • Unchanged — present in both A and B
  • Removed — only in A (deleted)
  • Added — only in B (inserted)

Unified diff format

The most common format is unified diff, which is what git diff produces:

--- a/config.txt
+++ b/config.txt
@@ -1,6 +1,7 @@
 server {
-    listen 80;
+    listen 443 ssl;
+    ssl_certificate /etc/ssl/cert.pem;
     server_name example.com;
 
-    root /var/www/html;
+    root /var/www/public;
 }

Reading the symbols:

Symbol Meaning
--- Path to file A (original)
+++ Path to file B (modified)
@@ Hunk header — shows which lines from each file
- Line removed from A
+ Line added in B
(space) Unchanged context line

The @@ -1,6 +1,7 @@ header means: "starting at line 1, show 6 lines from A; starting at line 1, show 7 lines from B." The difference (6 vs 7) reflects the net change in line count.

The diff algorithm

The classic algorithm is Longest Common Subsequence (LCS), developed by Myers in 1986 and still used in most diff tools today. It works by finding the largest set of lines that appear in the same order in both files, then marking everything else as added or removed.

In practice, diff tools optimize for:

  • Minimal edit distance — fewest additions and deletions
  • Semantic quality — grouping related changes into hunks
  • Performance — handling large files in milliseconds

Modern tools like git use the Myers algorithm by default, with options for patience diff (better for code) and histogram diff (better for copied code).

Diffing in JavaScript

The most popular library is diff by Ariya Hidayat:

import { diffLines, createPatch } from "diff";

const a = `line one\nline two\nline three\n`;
const b = `line one\nline 2\nline three\nline four\n`;

// Get structured diff
const changes = diffLines(a, b);
for (const part of changes) {
  const prefix = part.added ? "+" : part.removed ? "-" : " ";
  process.stdout.write(prefix + part.value);
}
// Output:
//  line one
// -line two
// +line 2
//  line three
// +line four

// Or generate a unified patch string
const patch = createPatch("file.txt", a, b);
console.log(patch);

For word-level diffs (useful for prose):

import { diffWords } from "diff";

const a = "The quick brown fox";
const b = "The slow brown fox jumps";

diffWords(a, b).forEach(part => {
  if (part.added)   process.stdout.write(`[+${part.value}]`);
  else if (part.removed) process.stdout.write(`[-${part.value}]`);
  else process.stdout.write(part.value);
});
// The [-quick][+slow] brown fox[+ jumps]

Diffing in Python

Python's standard library includes difflib — no install needed:

import difflib

a = "line one\nline two\nline three\n"
b = "line one\nline 2\nline three\nline four\n"

# Unified diff (same format as git diff)
diff = difflib.unified_diff(
    a.splitlines(keepends=True),
    b.splitlines(keepends=True),
    fromfile="original.txt",
    tofile="modified.txt",
)
print("".join(diff))

# HTML diff (for displaying in a browser)
html_diff = difflib.HtmlDiff()
html = html_diff.make_file(
    a.splitlines(),
    b.splitlines(),
    fromdesc="Original",
    todesc="Modified",
)
with open("diff.html", "w") as f:
    f.write(html)

# Similarity ratio (0.0 = totally different, 1.0 = identical)
ratio = difflib.SequenceMatcher(None, a, b).ratio()
print(f"Similarity: {ratio:.0%}")  # Similarity: 74%

Comparing two files on disk:

import difflib, pathlib, sys

file_a = pathlib.Path(sys.argv[1]).read_text()
file_b = pathlib.Path(sys.argv[2]).read_text()

diff = difflib.unified_diff(
    file_a.splitlines(keepends=True),
    file_b.splitlines(keepends=True),
    fromfile=sys.argv[1],
    tofile=sys.argv[2],
)
sys.stdout.writelines(diff)

Diffing in Go

The go-diff library implements the Myers algorithm:

package main

import (
	"fmt"
	"github.com/sergi/go-diff/diffmatchpatch"
)

func main() {
	dmp := diffmatchpatch.New()

	a := "line one\nline two\nline three\n"
	b := "line one\nline 2\nline three\nline four\n"

	// Character-level diff
	diffs := dmp.DiffMain(a, b, false)
	fmt.Println(dmp.DiffPrettyText(diffs))

	// Line-level diff (better for most files)
	aRunes, bRunes, lines := dmp.DiffLinesToRunes(a, b)
	diffs = dmp.DiffMainRunes(aRunes, bRunes, false)
	diffs = dmp.DiffCharsToLines(diffs, lines)

	for _, d := range diffs {
		switch d.Type {
		case diffmatchpatch.DiffInsert:
			fmt.Printf("+ %s", d.Text)
		case diffmatchpatch.DiffDelete:
			fmt.Printf("- %s", d.Text)
		case diffmatchpatch.DiffEqual:
			fmt.Printf("  %s", d.Text)
		}
	}
}

For a pure stdlib approach (simpler, line-level only):

package main

import (
	"bufio"
	"fmt"
	"strings"
)

func splitLines(s string) []string {
	var sc = bufio.NewScanner(strings.NewReader(s))
	var lines []string
	for sc.Scan() {
		lines = append(lines, sc.Text())
	}
	return lines
}

// Minimal LCS-based diff — good for small files
func diff(a, b []string) {
	// Build LCS table
	m, n := len(a), len(b)
	dp := make([][]int, m+1)
	for i := range dp {
		dp[i] = make([]int, n+1)
	}
	for i := m - 1; i >= 0; i-- {
		for j := n - 1; j >= 0; j-- {
			if a[i] == b[j] {
				dp[i][j] = dp[i+1][j+1] + 1
			} else if dp[i+1][j] > dp[i][j+1] {
				dp[i][j] = dp[i+1][j]
			} else {
				dp[i][j] = dp[i][j+1]
			}
		}
	}
	// Trace back
	i, j := 0, 0
	for i < m && j < n {
		if a[i] == b[j] {
			fmt.Printf("  %s\n", a[i]); i++; j++
		} else if dp[i+1][j] >= dp[i][j+1] {
			fmt.Printf("- %s\n", a[i]); i++
		} else {
			fmt.Printf("+ %s\n", b[j]); j++
		}
	}
	for ; i < m; i++ { fmt.Printf("- %s\n", a[i]) }
	for ; j < n; j++ { fmt.Printf("+ %s\n", b[j]) }
}

Diffing in PHP

// PHP has no built-in diff, but similar_text() gives similarity
$a = "line one\nline two\nline three\n";
$b = "line one\nline 2\nline three\nline four\n";

similar_text($a, $b, $percent);
echo "Similarity: " . round($percent, 1) . "%\n"; // Similarity: 74.3%

// For a real diff, use the shell:
function unifiedDiff(string $fileA, string $fileB): string {
    $a = escapeshellarg($fileA);
    $b = escapeshellarg($fileB);
    exec("diff -u {$a} {$b}", $output);
    return implode("\n", $output);
}

// Or use a library like jfcherng/php-diff:
// composer require jfcherng/php-diff
use Jfcherng\Diff\Differ;
use Jfcherng\Diff\DiffHelper;

$result = DiffHelper::calculate($a, $b, "Unified");
echo $result;

Common diff options (command line)

When using the diff command on Linux/macOS:

Flag Effect
-u Unified format (most readable, used by git)
-c Context format (older, used by some patch tools)
-i Ignore case differences
-w Ignore all whitespace
-b Ignore changes in amount of whitespace
--word-diff Word-level diff instead of line-level
-r Recurse into directories

Git adds its own layer:

git diff HEAD~1 HEAD          # changes in last commit
git diff --stat               # summary of changed files
git diff --word-diff          # word-level highlighting
git diff --ignore-all-space   # skip whitespace-only changes

When to use line-level vs character-level diff

Use case Best level
Source code Line
Prose / documents Word or character
JSON / YAML Line + collapse unchanged blocks
Binary files Don't diff — compare hashes instead
Large config files Line with context

Compare text online

If you need to quickly compare two blocks of text without writing code, the Text Diff tool does it in your browser. Paste two texts, get a color-coded diff instantly — added lines in green, removed in red, unchanged lines in grey. No account needed, nothing is uploaded to a server.

FAQ

Q: What's the difference between a diff and a patch? A diff is the output that shows what changed. A patch is a diff that can be applied to a file to reproduce the changes. They use the same format — the difference is intent.

Q: Can I diff binary files? Not meaningfully. Binary diff tools exist, but they compare bytes rather than lines and produce output that's hard to interpret. For documents like Word or PDF, convert to text first, then diff.

Q: Why does my diff show huge changes when I only changed one line? Usually a whitespace or line-ending issue. Try diff -b (ignore whitespace changes) or check that both files use the same line endings (CRLF vs LF). You can normalize endings with dos2unix or a text editor.

Q: What is a "merge conflict"? When two people edit the same lines differently, git can't automatically apply both changes. It marks the conflict with <<<<<<<, =======, and >>>>>>> markers and asks you to manually choose which version (or combine them). The diff algorithm is what detects that two changes overlap.

Q: Is the Myers algorithm always best? No. For code that has a lot of identical blocks (e.g., functions that look similar), the patience diff algorithm (git diff --patience) produces more readable output by preferring to align unique lines. Try both if the default output is confusing.

Related tools

Keep reading

All Toolmingotools are free & run in your browser

No sign-up, no upload, no watermark. Your files never leave your device.

Browse all tools