Utility Coder
← Back to Blog
Tools8 min read

Text Diff and Comparison: Developer Guide

Learn text comparison algorithms, diff formats, and tools. Understand how version control systems track changes.

By Andy Pham

Text Diff and Comparison: Developer Guide

Text diff tools show differences between two versions of text. This guide covers diff algorithms, formats, and practical applications.

Understanding Diff Output

Unified Diff Format

--- original.txt
+++ modified.txt
@@ -1,4 +1,5 @@
 Line 1 unchanged
-Line 2 removed
+Line 2 modified
+Line 2.5 added
 Line 3 unchanged
 Line 4 unchanged

Format explanation:

  • --- Old file header
  • +++ New file header
  • @@ Line range information
  • (space) Unchanged line
  • - Removed line
  • + Added line

Diff Algorithms

Longest Common Subsequence (LCS)

function lcs(text1, text2) {
  const m = text1.length;
  const n = text2.length;
  const dp = Array(m + 1).fill(null).map(() => Array(n + 1).fill(0));

  for (let i = 1; i <= m; i++) {
    for (let j = 1; j <= n; j++) {
      if (text1[i - 1] === text2[j - 1]) {
        dp[i][j] = dp[i - 1][j - 1] + 1;
      } else {
        dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
      }
    }
  }

  return dp[m][n];
}

Simple Line-by-Line Diff

function simpleDiff(original, modified) {
  const originalLines = original.split('
');
  const modifiedLines = modified.split('
');
  const result = [];

  let i = 0, j = 0;

  while (i < originalLines.length || j < modifiedLines.length) {
    if (i >= originalLines.length) {
      result.push({ type: 'add', line: modifiedLines[j] });
      j++;
    } else if (j >= modifiedLines.length) {
      result.push({ type: 'remove', line: originalLines[i] });
      i++;
    } else if (originalLines[i] === modifiedLines[j]) {
      result.push({ type: 'same', line: originalLines[i] });
      i++;
      j++;
    } else {
      result.push({ type: 'remove', line: originalLines[i] });
      result.push({ type: 'add', line: modifiedLines[j] });
      i++;
      j++;
    }
  }

  return result;
}

Character-Level Diff

function charDiff(str1, str2) {
  const result = [];
  let i = 0, j = 0;

  while (i < str1.length && j < str2.length) {
    if (str1[i] === str2[j]) {
      result.push({ type: 'same', char: str1[i] });
      i++;
      j++;
    } else {
      // Find next matching position
      const nextInStr2 = str2.indexOf(str1[i], j);
      const nextInStr1 = str1.indexOf(str2[j], i);

      if (nextInStr2 !== -1 && (nextInStr1 === -1 || nextInStr2 - j < nextInStr1 - i)) {
        while (j < nextInStr2) {
          result.push({ type: 'add', char: str2[j] });
          j++;
        }
      } else {
        result.push({ type: 'remove', char: str1[i] });
        i++;
      }
    }
  }

  while (i < str1.length) {
    result.push({ type: 'remove', char: str1[i++] });
  }
  while (j < str2.length) {
    result.push({ type: 'add', char: str2[j++] });
  }

  return result;
}

Visual Diff Display

function renderDiff(diff) {
  return diff.map(item => {
    switch (item.type) {
      case 'add':
        return `<span class="diff-add">+${item.line}</span>`;
      case 'remove':
        return `<span class="diff-remove">-${item.line}</span>`;
      default:
        return `<span class="diff-same"> ${item.line}</span>`;
    }
  }).join('
');
}

// CSS
const styles = `
.diff-add { background: #e6ffed; color: #22863a; }
.diff-remove { background: #ffeef0; color: #cb2431; }
.diff-same { color: #6a737d; }
`;

Side-by-Side Diff

function sideBySideDiff(original, modified) {
  const diff = simpleDiff(original, modified);
  const left = [];
  const right = [];

  for (const item of diff) {
    switch (item.type) {
      case 'same':
        left.push(item.line);
        right.push(item.line);
        break;
      case 'remove':
        left.push(item.line);
        right.push('');
        break;
      case 'add':
        left.push('');
        right.push(item.line);
        break;
    }
  }

  return { left, right };
}

Use Cases

1. Code Review

// Show changes between commits
function reviewChanges(oldCode, newCode) {
  const diff = simpleDiff(oldCode, newCode);
  const additions = diff.filter(d => d.type === 'add').length;
  const deletions = diff.filter(d => d.type === 'remove').length;

  return {
    diff,
    summary: `+${additions} -${deletions}`
  };
}

2. Document Comparison

function compareDocuments(doc1, doc2) {
  // Normalize whitespace
  const normalize = text => text.replace(/s+/g, ' ').trim();

  const words1 = normalize(doc1).split(' ');
  const words2 = normalize(doc2).split(' ');

  return simpleDiff(words1.join('
'), words2.join('
'));
}

3. Real-Time Sync

// Track changes in text editor
let lastContent = '';

function trackChanges(currentContent) {
  const changes = simpleDiff(lastContent, currentContent);
  lastContent = currentContent;
  return changes;
}

Best Practices

  1. Normalize line endings before comparing
  2. Ignore whitespace for semantic comparison
  3. Use word-level diff for prose
  4. Use line-level diff for code
  5. Show context around changes
function normalizeText(text) {
  return text
    .replace(/
/g, '
')  // Normalize line endings
    .replace(/	/g, '  ')    // Convert tabs to spaces
    .trimEnd();               // Remove trailing whitespace
}

Try Our Tools

Conclusion

Text diff tools are essential for version control, code review, and document comparison. Understanding diff algorithms helps you build better comparison features.

Share this article