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.
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.
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.
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.
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.
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.