Two or more sibling classes each carry identical collection-management code — add, remove, iterate, total. The duplicated methods change in lockstep; bug fixes happen N times; readers see the same algorithm in N files and can never be quite sure the copies stay consistent.
N copies of identical collection-management methods the agent must verify match on every edit. A bug fix in one copy must be ported to all others; the agent has no static guarantee the copies stayed consistent.
A Composite superclass owns the collection and its management methods; each sibling extends the Composite and declares only its distinctive concerns. Collection-related bug fixes land once and apply everywhere.
One Composite superclass the agent reads once and trusts thereafter. Sibling classes are short enough to load entirely in context; the agent verifies only the distinctive surface.
Before the refactoring
// Two sibling classes each carry the same line-item collection logic.class Order {constructor(customer) {this.customer = customer;this.lineItems = [];}addLineItem(item) { this.lineItems.push(item); }removeLineItem(item) {const index = this.lineItems.indexOf(item);if (index >= 0) this.lineItems.splice(index, 1);}total() { return this.lineItems.reduce((sum, i) => sum + i.amount, 0); }ship() { /* Order-specific */ }}class Invoice {constructor(customer, dueDate) {this.customer = customer;this.dueDate = dueDate;this.lineItems = [];}addLineItem(item) { this.lineItems.push(item); }removeLineItem(item) {const index = this.lineItems.indexOf(item);if (index >= 0) this.lineItems.splice(index, 1);}total() { return this.lineItems.reduce((sum, i) => sum + i.amount, 0); }markPaid() { /* Invoice-specific */ }}
After the refactoring
// Collection management lives once in a Composite superclass.class LineItemContainer {constructor(customer) {this.customer = customer;this.lineItems = [];}addLineItem(item) { this.lineItems.push(item); }removeLineItem(item) {const index = this.lineItems.indexOf(item);if (index >= 0) this.lineItems.splice(index, 1);}total() { return this.lineItems.reduce((sum, i) => sum + i.amount, 0); }}class Order extends LineItemContainer {ship() { /* Order-specific */ }}class Invoice extends LineItemContainer {constructor(customer, dueDate) {super(customer);this.dueDate = dueDate;}markPaid() { /* Invoice-specific */ }}
Duplicated collection logic is the classic Shotgun Surgery case — a single bug in the iteration semantics requires N edits. Subtle drift between copies (one sibling normalizes input, another doesn't) silently produces inconsistent behaviour.
Duplicated collection logic across N siblings × M methods = N×M cells the agent must hold to verify consistency. The agent's edit budget on a single collection-method change is N times what it would be with the Composite in place.
Inheritance couples the siblings; subsequent variation between them must either fit the superclass interface or break it. Extracting too aggressively (e.g., when only the data shape is shared but the semantics differ) produces a brittle base class that constrains every subclass.
Inheritance hides behaviour in the superclass that callers may not know to look for; the agent must traverse the class hierarchy to know what a sibling can do. Method resolution order issues complicate static reasoning when subclasses override partial behaviours.
Collection management lives in one file; siblings shrink to declare only their distinct surface. Adding a new container type (Quote, Receipt) becomes a one-line subclass declaration.
Collection-handling logic lives at the Composite class at one file; sibling classes hold only their leaf-specific work; tests against the Composite cover the tree-walking logic without per-sibling duplication of the same setup.
A Composite superclass that accumulates every behaviour any sibling needs becomes a god-base — siblings inherit methods they don't use, leak superclass concerns into their own logic, and resist further refactoring. Composition (has-a a LineItemContainer) is often the better answer.
A bloated Composite forces the agent to load a large superclass before reading any sibling. If sibling behaviours diverge later, the agent must constantly cross-check superclass methods against per-sibling overrides — context cost migrates from duplication to inheritance traversal.