# Collapse Hierarchy

> Merge a subclass into its superclass (or the reverse) when the two have become nearly identical and the split no longer earns its cost.

- kind: technique
- category: dealing-with-generalization
- canonical: https://aipatterns.dev/refactoring/techniques/collapse-hierarchy
- inspired-by: https://refactoring.guru/collapse-hierarchy

## Problem

A subclass adds almost nothing to its superclass. Perhaps features were moved away over time, or the subclass was created for a variation that never arrived. Readers have to open two files to understand one concept.

## Solution

**Move any remaining members into one of the two classes, redirect all references to it, and delete the other.**

## Before

```ts
class Notifier {
  constructor(protected transport: Transport) {}

  async notify(userId: string, message: string): Promise<void> {
    const user = await users.find(userId);
    if (user.mutedUntil && user.mutedUntil > new Date()) return;
    await this.transport.send(user.contact, message);
  }
}

// Created for a planned "priority" feature that never shipped
class DefaultNotifier extends Notifier {
  constructor(transport: Transport) {
    super(transport);
  }
}

const notifier: Notifier = new DefaultNotifier(smtpTransport);
```

## After

```ts
class Notifier {
  constructor(private transport: Transport) {}

  async notify(userId: string, message: string): Promise<void> {
    const user = await users.find(userId);
    if (user.mutedUntil && user.mutedUntil > new Date()) return;
    await this.transport.send(user.contact, message);
  }
}

const notifier = new Notifier(smtpTransport);
```

## Why

- One class to read, with no question about which one to extend or instantiate.
- `protected` members can become `private` once no subclass exists.

## Steps

1. Choose which class to keep. Usually keep the one whose name fits the concept and has more references.
2. Move the remaining members of the removed class into the kept one with [Pull Up Field](https://aipatterns.dev/refactoring/techniques/pull-up-field.md), [Pull Up Method](https://aipatterns.dev/refactoring/techniques/pull-up-method.md), or their push-down counterparts. Run the tests.
3. Replace every reference to the removed class (constructors, type annotations, `instanceof`, DI bindings) with the kept class. Run the tests.
4. Delete the empty class. Tighten visibility of members that were `protected` only for the subclass. Run the tests.

## Watch out

- Do not collapse a hierarchy that other subclasses still extend, or that is part of a public extension point for library users.
- Check `instanceof` checks and serialized type discriminators (`"type": "DefaultNotifier"` in stored JSON or queues). Old data may still reference the removed class.
- If the subclass carries real behavior for some instances, collapsing reintroduces flags and branches. That is the case for [Extract Subclass](https://aipatterns.dev/refactoring/techniques/extract-subclass.md).
- The inverse techniques are [Extract Subclass](https://aipatterns.dev/refactoring/techniques/extract-subclass.md) and [Extract Superclass](https://aipatterns.dev/refactoring/techniques/extract-superclass.md).

## Related

- Inverse: [Extract Subclass](https://aipatterns.dev/refactoring/techniques/extract-subclass.md), [Extract Superclass](https://aipatterns.dev/refactoring/techniques/extract-superclass.md).
- Fixes [Lazy Class](https://aipatterns.dev/refactoring/smells/lazy-class.md), [Speculative Generality](https://aipatterns.dev/refactoring/smells/speculative-generality.md), and helps untangle [Parallel Inheritance Hierarchies](https://aipatterns.dev/refactoring/smells/parallel-inheritance-hierarchies.md).
- The inheritance analog of [Inline Class](https://aipatterns.dev/refactoring/techniques/inline-class.md).

## Agent checklist

- [ ] The removed class adds no meaningful behavior or state.
- [ ] No other code, plugin or public API extends the removed class.
- [ ] All references, including `instanceof`, DI bindings and stored type names, were redirected.
- [ ] Visibility was tightened after the collapse.
