Guides

How Repeated XML Elements Are Compared: By Index vs LCS vs Unordered

Oct 3, 2026 · 4 min read

The docs introduce the three comparison methods for repeated elements in a paragraph each. That’s enough to pick one — not enough to predict what the diff will actually say. This post runs the same XML through all three and shows the exact output, because the differences are bigger than the names suggest.

First, a one-line bridge from XML to the mechanics (detailed in how XML diff works): repeated sibling elements — three <event> tags under one <log> — become an array in the data tree. The comparison option you’re choosing applies to exactly those arrays:

  • By Index pairs event[0] with event[0], and recurses.
  • LCS finds the longest subsequence of deep-equal elements; the rest are insertions and deletions.
  • Unordered treats the siblings as a multiset: each base element consumes one deep-equal match.

Elements that appear only once per parent are matched by name, so this option only matters where the same tag repeats.

Scenario 1: one element inserted in the middle

A log gains a single entry as the second event:

<log><event>1</event><event>2</event><event>3</event><event>4</event></log>
<log><event>1</event><event>5</event><event>2</event><event>3</event><event>4</event></log>

By Index watches the positional alignment shift and reports a cascade:

valueChanged  log.event[1]
valueChanged  log.event[2]
valueChanged  log.event[3]
added         log.event[4]

Four differences for one insertion. LCS isolates the common subsequence and says exactly what happened:

added  log.event[1]

Unordered agrees:

added  log.event[1]

Scenario 2: pure reordering

Same steps, different order:

<steps><step>build</step><step>test</step><step>deploy</step></steps>
<steps><step>deploy</step><step>build</step><step>test</step></steps>

By Index flags every position:

valueChanged  steps.step[0]
valueChanged  steps.step[1]
valueChanged  steps.step[2]

LCS keeps the longest ordered run it can and moves the rest:

deleted  steps.step[2]
added    steps.step[0]

Unordered stays silent:

(no differences)

Which answer is right? If this is an Ant build file or a WS-Security header where element order is contractual, By Index’s alarm is the correct one. If it’s a list of <tag> or <permission> entries, Unordered’s silence is. The wrong choice either buries you in noise or hides a real regression.

Scenario 3: an element modified in place

The trade-off the docs don’t spell out. A child element changes inside a repeated block:

<users>
  <user id="1"><role>admin</role></user>
  <user id="2"><role>user</role></user>
</users>
<users>
  <user id="1"><role>admin</role></user>
  <user id="2"><role>owner</role></user>
</users>

By Index recurses into the pair and pinpoints the exact spot — attributes, children, all of it:

valueChanged  users.user[1].role

LCS matches elements by deep equality, and the second <user> no longer qualifies. The whole element drops out of the subsequence:

deleted  users.user[1]
added    users.user[1]

Unordered reports the same pair. You learn that user[1] differs, but not whether it was the id attribute, the <role>, or anything else. For structured repeated elements — <server> blocks in a config, <field> definitions in a schema — By Index is the only method that preserves field-level precision.

Scenario 4: duplicates are counted, not ignored

Unordered is multiset matching, not set matching — each match is consumed one-for-one:

<post><tag>a</tag><tag>b</tag><tag>b</tag><tag>c</tag></post>
<post><tag>c</tag><tag>a</tag><tag>b</tag></post>

Two <tag>b</tag> on the left, one on the right:

deleted  post.tag[2]

If repetition counts are significant, that’s the behavior you want. If you truly want “same unique values”, deduplicate first — or expect Unordered to flag count differences.

Performance: the hidden axis

MethodTime complexityNotes
By IndexO(n) comparisonsPlus one recursive comparison per pair
LCSO(m × n) deep-equality checksEach DP cell may recursively compare two entire subtrees
UnorderedO(m × n) worst caseEvery unmatched base element scans the contrast side

For a config with twenty <server> entries, any method is instant. For a data export with ten thousand <record> elements, LCS’s table — a full recursive comparison per cell — is genuinely expensive, and Unordered’s quadratic scan isn’t far behind. By Index is the only method that’s essentially free at any size.

Choosing with eyes open

Your repeated elements are…Best methodWhat you give up
Positional (pipeline steps, table rows, coordinates)By IndexInsertions shift everything — noise on misaligned lists
Ordered, occasionally edited (logs, changelog entries)LCSField-level precision on modified elements; O(m·n) cost
Orderless (tags, permissions, feature flags)UnorderedOrder changes are invisible; whole-element add/delete reporting

The one-line version: By Index is precise but brittle, LCS is minimal but shallow, Unordered is calm but blind to order.

Try it on your own documents — the method switch lives in Settings, and re-running a comparison takes seconds: compare two XML documents now.