Introduction
Finding the difference between two strings in JavaScript depends on what kind of difference you need. For character-level diffs, iterate through both strings and compare at each index. For word-level or line-level diffs, split the strings and compare the resulting arrays. For production use cases like showing text changes (similar to Git diffs), use a library like diff (npm). JavaScript has no built-in diff function, so you must implement or import one.
Character-by-Character Comparison
1function findCharDifferences(str1, str2) {
2 const diffs = [];
3 const maxLen = Math.max(str1.length, str2.length);
4
5 for (let i = 0; i < maxLen; i++) {
6 if (str1[i] !== str2[i]) {
7 diffs.push({
8 index: i,
9 str1: str1[i] || '(missing)',
10 str2: str2[i] || '(missing)'
11 });
12 }
13 }
14 return diffs;
15}
16
17const diffs = findCharDifferences('hello world', 'hallo werld');
18console.log(diffs);
19// [
20// { index: 1, str1: 'e', str2: 'a' },
21// { index: 7, str1: 'o', str2: 'e' }
22// ]
This identifies positions where characters differ. It handles strings of different lengths by treating missing characters as differences.
Finding Added/Removed Characters
1function findUniqueChars(str1, str2) {
2 const set1 = new Set(str1);
3 const set2 = new Set(str2);
4
5 const onlyInStr1 = [...set1].filter(ch => !set2.has(ch));
6 const onlyInStr2 = [...set2].filter(ch => !set1.has(ch));
7
8 return { removed: onlyInStr1, added: onlyInStr2 };
9}
10
11console.log(findUniqueChars('abcdef', 'abcxyz'));
12// { removed: ['d', 'e', 'f'], added: ['x', 'y', 'z'] }
This compares character sets — it tells you which characters exist in one string but not the other, without considering position.
Word-Level Difference
1function findWordDifferences(str1, str2) {
2 const words1 = str1.split(/\s+/);
3 const words2 = str2.split(/\s+/);
4
5 const removed = words1.filter(w => !words2.includes(w));
6 const added = words2.filter(w => !words1.includes(w));
7
8 return { removed, added };
9}
10
11const result = findWordDifferences(
12 'the quick brown fox jumps',
13 'the slow brown cat jumps'
14);
15console.log(result);
16// { removed: ['quick', 'fox'], added: ['slow', 'cat'] }
Longest Common Subsequence (LCS)
The LCS algorithm finds the longest sequence of characters common to both strings, which helps identify what was kept versus what changed:
1function lcs(str1, str2) {
2 const m = str1.length;
3 const n = str2.length;
4 const dp = Array.from({ length: m + 1 }, () => Array(n + 1).fill(0));
5
6 for (let i = 1; i <= m; i++) {
7 for (let j = 1; j <= n; j++) {
8 if (str1[i - 1] === str2[j - 1]) {
9 dp[i][j] = dp[i - 1][j - 1] + 1;
10 } else {
11 dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
12 }
13 }
14 }
15
16 // Backtrack to find the LCS string
17 let result = '';
18 let i = m, j = n;
19 while (i > 0 && j > 0) {
20 if (str1[i - 1] === str2[j - 1]) {
21 result = str1[i - 1] + result;
22 i--; j--;
23 } else if (dp[i - 1][j] > dp[i][j - 1]) {
24 i--;
25 } else {
26 j--;
27 }
28 }
29
30 return result;
31}
32
33console.log(lcs('abcdef', 'acbcf')); // "abcf"
The LCS is the foundation of diff algorithms. Characters not in the LCS are the "differences."
Using the diff Library
For production applications, use the diff npm package:
1import { diffChars, diffWords, diffLines } from 'diff';
2
3// Character-level diff
4const charDiff = diffChars('hello world', 'hallo werld');
5charDiff.forEach(part => {
6 const prefix = part.added ? '+' : part.removed ? '-' : ' ';
7 process.stdout.write(`${prefix}${part.value}`);
8});
9// Output: h-e+allo w-o+erld
10
11// Word-level diff
12const wordDiff = diffWords(
13 'the quick brown fox',
14 'the slow brown cat'
15);
16wordDiff.forEach(part => {
17 if (part.added) console.log(`+ ${part.value}`);
18 else if (part.removed) console.log(`- ${part.value}`);
19 else console.log(` ${part.value}`);
20});
21// Output:
22// the
23// - quick
24// + slow
25// brown
26// - fox
27// + cat
28
29// Line-level diff
30const lineDiff = diffLines(
31 'line1\nline2\nline3',
32 'line1\nmodified\nline3'
33);
Levenshtein Distance (Edit Distance)
The edit distance tells you the minimum number of single-character edits (insertions, deletions, substitutions) to transform one string into another:
1function levenshtein(a, b) {
2 const m = a.length, n = b.length;
3 const dp = Array.from({ length: m + 1 }, (_, i) =>
4 Array.from({ length: n + 1 }, (_, j) => (i === 0 ? j : j === 0 ? i : 0))
5 );
6
7 for (let i = 1; i <= m; i++) {
8 for (let j = 1; j <= n; j++) {
9 if (a[i - 1] === b[j - 1]) {
10 dp[i][j] = dp[i - 1][j - 1];
11 } else {
12 dp[i][j] = 1 + Math.min(
13 dp[i - 1][j], // deletion
14 dp[i][j - 1], // insertion
15 dp[i - 1][j - 1] // substitution
16 );
17 }
18 }
19 }
20
21 return dp[m][n];
22}
23
24console.log(levenshtein('kitten', 'sitting')); // 3
25console.log(levenshtein('hello', 'hallo')); // 1
Highlighting Differences in HTML
1import { diffChars } from 'diff';
2
3function highlightDiff(oldStr, newStr) {
4 const diff = diffChars(oldStr, newStr);
5
6 return diff.map(part => {
7 if (part.added) return `<ins style="background:#d4edda">${part.value}</ins>`;
8 if (part.removed) return `<del style="background:#f8d7da">${part.value}</del>`;
9 return part.value;
10 }).join('');
11}
12
13const html = highlightDiff('the quick fox', 'the slow cat');
14// "the <del>quick fox</del><ins>slow cat</ins>"
Comparing Strings Ignoring Case/Whitespace
1function normalize(str) {
2 return str.toLowerCase().replace(/\s+/g, ' ').trim();
3}
4
5function areSimilar(str1, str2) {
6 return normalize(str1) === normalize(str2);
7}
8
9console.log(areSimilar('Hello World', 'hello world')); // true
10
11// Find meaningful differences after normalization
12function normalizedDiff(str1, str2) {
13 return findCharDifferences(normalize(str1), normalize(str2));
14}
Common Pitfalls
Unicode and emoji: String indexing in JavaScript uses UTF-16 code units. Characters like emoji ("😀".length === 2) span two indices. Use Array.from(str) or the spread operator [...str] to split by code points instead of code units.
Performance with long strings: Character-by-character comparison is O(n), but LCS and Levenshtein are O(n*m). For very long strings (10,000+ characters), these algorithms use significant memory and time. Use the diff library which has optimized implementations.
Set-based comparison loses position: Using Set to find unique characters tells you what changed but not where. For positional differences, use index-based comparison or a diff algorithm.
Case sensitivity: 'Hello' !== 'hello' in JavaScript. If case differences are not meaningful, normalize both strings with .toLowerCase() before comparing.
Whitespace differences: Trailing spaces, different line endings (\n vs \r\n), and tab-vs-space differences create noise. Normalize whitespace before comparing if these differences are not relevant.
Summary
For character-by-character differences, iterate with index comparison
For word-level differences, split strings and compare arrays
Use the diff npm package for production-quality diffs (character, word, or line level)
Levenshtein distance measures how many edits are needed to transform one string into another
LCS finds the longest common subsequence, which is the basis of most diff algorithms
Normalize strings (case, whitespace) before comparing when those differences are irrelevant