Skip to content

Difftastic — Structural Diff Tool for Smarter Code Review

DodaTech Updated 2026-06-24 8 min read

In this tutorial, you'll learn about Difftastic. We cover key concepts, practical examples, and best practices to help you understand and apply this topic effectively.

Difftastic is a structural diff tool that understands syntax — comparing code at the AST level rather than line-by-line — producing diffs that show actual changes inside functions and expressions without noise from whitespace or reformatting.

What You'll Learn

How Difftastic parses code into ASTs and diffs at the expression level, integrates with Git as an external diff tool, handles moved and reformatted code, compares JSON and other structured data, and works with 30+ programming languages.

Why Difftastic Matters

Traditional line-based diffs (git diff) break when code is reformatted, when whitespace changes, or when functions are moved. A reformatted function shows as entirely deleted and re-added. Difftastic parses before/after into abstract syntax trees and compares tree nodes. A reformatted function shows zero changes. A moved block shows as moved, not deleted-and-added. This makes code review faster and more accurate. Doda Browser uses Difftastic for all Pull Request reviews — developers see semantic changes, not formatting noise.

Learning Path

flowchart LR
  A[hyperfine] --> B[Difftastic
You are here] B --> C[procs & bottom] B --> D[bat & delta] style B fill:#f90,color:#fff

Installation

# macOS
brew install difftastic

# Ubuntu/Debian
sudo apt install difftastic

# Fedora
sudo dnf install difftastic

# Arch
sudo pacman -S difftastic

# Cargo
cargo install difftastic

# Download binary
curl -LO https://github.com/Wilfred/difftastic/releases/download/0.57.0/difft-x86_64-unknown-linux-gnu.tar.gz
tar xzf difft-x86_64-unknown-linux-gnu.tar.gz
sudo install difft /usr/local/bin/

Basic Usage

# Compare two files
difft old.py new.py

# Compare two directories
difft src/ src-modified/

# Diff against stdin
echo '{"a":1}' | difft - --language json <(echo '{"a":2}')

Expected output for a simple change:

# old.py
def greet(name):
    return f"Hello, {name}!"

# new.py
def greet(name, title=""):
    return f"Hello, {title} {name}!".strip()
1 1   def greet(
2   -     name
    2 +     name,
    3 +     title="",
    4   ):
3 5       return (
4   -         f"Hello, {name}!"
    6 +         f"Hello, {title} {name}!".strip()
5 7       )

Git Integration

As an External Diff Tool

# Configure Git to use difftastic
git config --global diff.external difft

# Or use it only when invoked explicitly
git config --global alias.dft '!difft'

# Then:
git dft                    # Show unstaged changes
git dft --staged           # Show staged changes
git dft HEAD~1 HEAD        # Compare two commits
# Configure difftastic as the diff pager
git config --global pager.difftool true
git config --global diff.tool difftastic
git config --global difftool.difftastic.cmd 'difft "$LOCAL" "$REMOTE"'

Language Support

Difftastic supports 30+ languages with tree-sitter grammars:

# Check if a language is supported
difft --check-language file.rs

# Manually specify language
difft --language python old.py new.py
difft --language json data-old.json data-new.json

# List all supported languages
difft --list-languages

Supported languages include:

  • Python, JavaScript, TypeScript, JSX, TSX
  • Rust, Go, Java, Kotlin
  • C, C++, C#, Objective-C
  • Ruby, PHP, Perl
  • Swift, Scala, Haskell, Elm
  • JSON, YAML, TOML, XML
  • CSS, SCSS, HTML
  • Markdown, LaTeX

Understanding Structural Diffs

Reformatted Code Shows Zero Changes

// Old (one line)
const items = [1, 2, 3, 4, 5, 6, 7, 8];

// New (formatted across lines)
const items = [
  1, 2, 3, 4,
  5, 6, 7, 8,
];

Difftastic shows: no changes. The AST is identical. Traditional diff shows all 8 lines as changed.

Renamed Functions

# Old
def process_data(input):
    result = transform(input)
    return validate(result)

# New (function renamed)
def process_and_validate(input):
    result = transform(input)
    return validate(result)

Difftastic shows only the function name as changed, not the entire body.

Whitespace-Only Changes

# Old
x = 1
y = 2

# New (indentation change)
  x = 1
  y = 2

Show as no changes (AST is identical).

Code Movement

When code is moved within a file, Difftastic shows it as moved rather than as a delete + add:

    1 1   def first():
    2 2       return "first"
        3 +
    4   - def second():
    5   -     return "second"
        4 + def third():
        5 +     return "third"
    3 6
    7   - def third():
    8   -     return "third"
        7 + def second():
        8 +     return "second"

Advanced Features

Display Modes

# Side-by-side display (default on wide terminals)
difft --display side-by-side old.py new.py

# Inline display
difft --display inline old.py new.py

Context Control

# Show more context around changes
difft --context 10 old.py new.py

# Show only changed lines
difft --context 0 old.py new.py

File Filtering

# Only compare specific file types
difft --extension py src/ src-modified/

# Exclude files matching a pattern
difft --exclude 'node_modules' src/ src-modified/

# Compare only specific files
difft old/src/main.py new/src/main.py

Tab Width

# Set tab width for display
difft --tab-width 4 old.py new.py

Integration with delta

delta and Difftastic serve different purposes — use them together:

# delta for Git diff formatting (line-based)
# Difftastic for structural diffs (AST-based)

# Configure both:
git config --global core.pager delta
git config --global diff.external difft

# Use delta for git log, difftastic for structural comparisons
git log -p    # delta-formatted
difft HEAD~1 HEAD  # AST-aware diff

Real-World Use Cases

Code Review Before Commit

# See what you actually changed (ignoring formatting)
difft --staged

# Compare your branch with main
difft main...HEAD

Detecting Unintended Changes

# After a large refactor, verify no logic changed
difft refactored/ original/

# Compare generated files
difft --language json api-spec-v1.json api-spec-v2.json

Merge Conflict Resolution

# Compare merge conflict sides
difft file.conflict.orig file.conflict.local
difft file.conflict.orig file.conflict.remote

Common Errors

1. "Unsupported language" Error

Difftastic does not have a tree-sitter grammar for this file type. Specify the language manually: difft --language python file.xyz. Add support by contributing a grammar.

2. Binary Files Show No Diff

Difftastic only works with text files. Binary files (images, PDFs) are skipped with a note. Use --force to attempt comparison anyway.

3. Large Files Are Slow

Parsing and diffing ASTs is computationally expensive for large files. Files over ~10,000 lines may be slow. Consider narrowing the comparison scope.

4. Difftastic Shows No Changes for Obvious Differences

The files may have the same AST but different formatting. Difftastic considers reformatted identical code as no change. Use --display inline to verify.

5. Git Integration Shows "external diff died" Error

The difft binary is not in Git's PATH. Use the full path in config: git config --global diff.external /usr/local/bin/difft.

6. Difftastic Output Too Wide for Terminal

Use --display inline for narrow terminals, or set --width 80 to cap the output width.

7. Moved Code Not Detected

Code movement detection works best with contiguous blocks. Interleaved changes may not be recognized as moves.

Practice Questions

1. How does Difftastic differ from traditional line-based diff tools? Difftastic parses code into ASTs and compares tree nodes. Traditional tools compare lines as text. This means Difftastic ignores formatting, whitespace, and code movement that would appear as changes in line-based diffs.

2. How do you configure Difftastic as Git's external diff tool? git config --global diff.external difft — then all git diff commands use Difftastic.

3. What happens when you compare reformatted code with Difftastic? If the AST is identical, Difftastic shows no changes. The reformatting is invisible.

4. Which programming languages does Difftastic support? 30+ languages including Python, JavaScript, TypeScript, Rust, Go, Java, C++, Ruby, and JSON. Run difft --list-languages for the full list.

5. How do you compare JSON files structurally with Difftastic? difft --language json old.json new.json — it ignores key ordering and shows structural differences in JSON values.

Challenge: Take a real project and intentionally: (1) reformat a file (change indentation, break long lines), (2) rename a function, (3) move a function to a different position in the file, (4) make an actual logic change. Run Difftastic on each change and observe which are detected as changes and which are not. Compare with git diff. Then set up Difftastic as your Git diff tool and use it for a week of code review.

Is Difftastic a replacement for code review tools?

No — Difftastic replaces git diff for showing changes. Code review still requires human judgement. Difftastic removes the noise so reviewers focus on semantic changes.

Does Difftastic work with patch files?

Yes — difft --language python < file.patch can parse unified diff format alongside the original files.

Can Difftastic diff three files (merge)?

Not natively. Use Git's mergetool for three-way merges. Difftastic works on two-file comparisons.

How does Difftastic handle generated code?

Generated code (protobuf, GraphQL) works if the output language has a grammar. The structural diff will show actual semantic changes even if line numbers shift.

What is the performance overhead of AST parsing?

Initial parsing takes 50-200ms per file depending on size. Subsequent diffs are fast because parsing is cached.

What's Next

procs & bottom — Modern ps & top
bat & delta — Better cat & Diff
Lazygit — Terminal Git UI Guide

Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro. Updated 2026-06-24.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro