Skip to content

Reading a Diff Properly

Sep 2, 2026 · Web Development

A unified diff is a compact description of how to turn one version of a file into another: it shows only the changed regions, marks removed lines with a leading minus and added lines with a leading plus, and surrounds them with a few unchanged context lines so the change can be located. Once you can read the header and the three line prefixes fluently, code review, merge conflicts, and patch files all stop being intimidating. This is the format that git diff, pull requests, and patch all speak, so it is worth ten minutes to learn precisely.

What does a unified diff look like?

Here is a small but complete example. Read it top to bottom and we will name every part:

--- a/greeting.js
+++ b/greeting.js
@@ -1,6 +1,6 @@
 function greet(name) {
-  const msg = "Hi " + name;
-  console.log(msg);
+  const msg = `Hello, ${name}!`;
+  console.info(msg);
   return msg;
 }

The first two lines identify the files. --- marks the old version (conventionally prefixed a/) and +++ marks the new version (b/). These two are file markers, not content; do not confuse the triple --- with a removed line.

Everything after is one or more hunks: contiguous regions of change with their surrounding context.

What does the @@ hunk header mean?

The @@ line tells you where the hunk sits in both files, as two ranges. In the example, @@ -1,6 +1,6 @@ reads as:

Token Meaning
-1,6 In the OLD file, this hunk starts at line 1 and spans 6 lines
+1,6 In the NEW file, this hunk starts at line 1 and spans 6 lines

The minus range always describes the old file, the plus range the new file. If a count is omitted (e.g. @@ -42 +42 @@) it means a single line. When lines are purely added, the old count can be 0; when purely removed, the new count can be 0. Some tools append a function name after the closing @@ — that is a hint about which function the hunk lives in, not part of the range.

Because the header carries line numbers, you can jump straight to the right place in the full file. And because a hunk includes context, a patch tool can still apply it even if earlier edits pushed those lines a little up or down.

What do +, -, and the leading space mean?

The very first character of each hunk line is a marker, not part of the code. There are exactly three:

Prefix Meaning
- (minus) Line removed: present in old, absent in new
+ (plus) Line added: absent in old, present in new
  (space) Context: unchanged, shown on both sides

Applying the marks to the example: two lines were removed (the old msg assignment and the console.log), two were added (the template-literal version and console.info), and the function greet, return msg;, and } lines are unchanged context. A modification is simply a removal immediately followed by an addition — the diff format has no separate “changed” marker.

One subtlety: because the marker occupies column one, the real content begins in column two. When you copy a line out of a diff, strip that first character or you will paste a stray + into your code.

Why are context lines shown, and how many?

Context lines — the unchanged lines around a change — exist so both humans and tools can locate the hunk reliably. The default is three lines above and three below each change. For a human reader, that context answers “what function is this in, and what surrounds the edit?” without opening the full file. For the patch program, context is how it finds the right spot: it does not blindly trust the line numbers, it searches for the surrounding context, which lets a patch apply cleanly even after the file has drifted.

If two changes are close together, their context overlaps and they merge into a single hunk rather than repeating shared lines. If they are far apart, you get separate hunks each with its own @@ header. More context makes a patch more robust but larger; less context makes it terse but more likely to misapply. Three is the conventional balance.

Line-level vs word-level: which should I read?

A line-level diff treats the whole line as the unit of change; a word-level (or character-level) diff highlights only the tokens that differ within a line. Line-level is the default and is right most of the time. But it has a blind spot: change one word on a long line and the line-level view marks the entire line removed and the entire line added, leaving you to spot the difference by eye.

Consider fixing a typo in a long string. Line-level shows:

-  return "Your acount balance is " + n;
+  return "Your account balance is " + n;

You have to scan both lines to find that acount became account. A word-level diff instead marks just the differing token, so the single-letter fix jumps out immediately. Word-level is invaluable for prose, renamed identifiers, and small edits buried in long lines; line-level is better for seeing structural changes where whole blocks moved. Good diff viewers let you switch, and combining the two — line-level to see which lines changed, word-level to see what within them — is the fastest way to review. A browser-based diff checker can show both views on two pasted snippets and, because it runs locally, you can compare config files or logs without uploading them anywhere.

When should I ignore whitespace?

Ignore whitespace when formatting changes are drowning out logic changes. Reindentation after wrapping code in an if block, converting tabs to spaces, or stripping trailing spaces can each mark dozens of lines as changed while not one character of behavior moved. A whitespace-insensitive diff collapses that noise so the substantive edits stand out.

Most tools offer a few levels:

Mode Effect
Ignore trailing whitespace Differences only at end of line are hidden
Ignore amount of whitespace One space vs several, or tab vs spaces, treated as equal
Ignore all whitespace Whitespace differences ignored entirely, even indentation

Use these with care. In languages where whitespace is significant — Python indentation, YAML nesting, or anything inside a string literal or a Makefile’s tab-indented recipes — ignoring whitespace can hide a real, behavior-changing edit. The safe habit is to review once ignoring whitespace to understand the logic, then glance at the raw diff to confirm no meaningful spacing changed.

Reading a diff in practice

Put it together and a review becomes a quick, ordered scan:

  1. Read the --- / +++ lines to know which file you are looking at.
  2. For each @@ header, note the line numbers so you know where in the file you are.
  3. Read - lines as “what left” and + lines as “what arrived”; a - followed by a + is a modification.
  4. Use the space-prefixed context to understand the surroundings.
  5. If a changed line looks nearly identical to its neighbor, switch to a word-level view to find the exact token.
  6. If the diff looks huge, toggle ignore-whitespace to check whether it is really just reformatting.

The whole format is built around one idea: show the smallest amount of information needed to reconstruct the change and to find it again. Removed and added lines carry the change, context lines carry the location, and the hunk header ties it to real line numbers. Learn those three pieces and every diff — a code review, a stubborn merge conflict, an emailed patch — reads the same way.

Frequently asked questions

What do the @@ symbols mean in a diff?

The @@ line is a hunk header. It contains two ranges, like @@ -12,7 +12,8 @@, meaning the old file section starts at line 12 for 7 lines and the new section starts at line 12 for 8 lines.

What is the difference between a + and a - line?

A line beginning with - was removed (present in the old file, gone in the new). A line beginning with + was added (absent in the old file, present in the new). A leading space means the line is unchanged context.

What are context lines and why are they shown?

Context lines are the unchanged lines shown around each change, by default three on each side. They let a reader and a patch tool locate the change even if surrounding line numbers have shifted.

When should I use a word-level diff instead of line-level?

Use a word-level diff when a line changed only slightly, such as one renamed variable or a fixed typo. It highlights the exact tokens that differ instead of marking the whole line removed and re-added.

Why does ignoring whitespace matter?

Reindentation, tab-vs-space changes, and trailing spaces can make a diff look large while no logic changed. Ignoring whitespace collapses that noise so you can see the real edits.