# Consolidate Duplicate Conditional Fragments

> Move code that runs identically in every branch of a conditional to before or after the conditional so each branch holds only what differs.

- kind: technique
- category: simplifying-conditional-expressions
- canonical: https://aipatterns.dev/refactoring/techniques/consolidate-duplicate-conditional-fragments
- inspired-by: https://refactoring.guru/consolidate-duplicate-conditional-fragments

## Problem

Every branch of an `if/else` or `switch` ends (or starts) with the same statements. The duplication hides what actually varies between branches, and a fix applied to one copy is easily forgotten in the others.

## Solution

**Hoist the shared statements out of the conditional: leading duplicates go before it, trailing duplicates go after it.** Each branch keeps only its unique work.

## Before

```ts
async function reserveStock(sku: string, qty: number, warehouse: Warehouse): Promise<void> {
  const item = await warehouse.find(sku);
  if (item.onHand >= qty) {
    item.onHand -= qty;
    item.reserved += qty;
    await warehouse.save(item);
    audit.log("reserve", sku, qty);
  } else if (item.backorderable) {
    item.backordered += qty - item.onHand;
    item.reserved += item.onHand;
    item.onHand = 0;
    await warehouse.save(item);
    audit.log("reserve", sku, qty);
  } else {
    throw new OutOfStockError(sku);
  }
}
```

## After

```ts
async function reserveStock(sku: string, qty: number, warehouse: Warehouse): Promise<void> {
  const item = await warehouse.find(sku);
  if (item.onHand >= qty) {
    item.onHand -= qty;
    item.reserved += qty;
  } else if (item.backorderable) {
    item.backordered += qty - item.onHand;
    item.reserved += item.onHand;
    item.onHand = 0;
  } else {
    throw new OutOfStockError(sku);
  }
  await warehouse.save(item);
  audit.log("reserve", sku, qty);
}
```

## Why

- The save and the audit line exist once, so they cannot drift apart.
- Each branch now shows only the stock arithmetic that differs.
- The function's shape (decide, then persist) is visible.

## Steps

1. Find statements that are textually and semantically identical at the start or end of every branch, including any `else` and `default`.
2. Check that a branch which exits early (by `throw`, `return`, `continue`) does not receive code it previously skipped. Here the `throw` branch never saved, and after the move it still never reaches the save.
3. Move trailing duplicates below the conditional, or leading duplicates above it. Run the tests.
4. If the duplicate sits in the middle of each branch, check whether the statements around it can be reordered safely. If not, extract it into a function instead.
5. For duplicates inside `try` and `catch`, move them to `finally` only if they must also run on failure.

## Watch out

- Do not hoist code past a branch that exits early unless that branch also ran it before.
- Leading duplicates moved above the conditional run before the condition is evaluated. If the condition reads state those statements change, the result changes.
- Statements that look identical may reference variables assigned differently per branch. Compare the values, not only the text.
- If only some branches share the fragment, extract a function rather than restructuring the conditional.

## Related

- Fixes [Duplicate Code](https://aipatterns.dev/refactoring/smells/duplicate-code.md) inside conditionals.
- Combine with [Consolidate Conditional Expression](https://aipatterns.dev/refactoring/techniques/consolidate-conditional-expression.md) when the branches also share a condition.
- Use [Extract Method](https://aipatterns.dev/refactoring/techniques/extract-method.md) for a shared fragment that cannot move.

## Agent checklist

- [ ] The moved code appeared in every branch, including the `else`/`default`.
- [ ] No branch that previously exited early now executes the moved code.
- [ ] Moved leading code does not affect the condition it now precedes.
- [ ] Tests cover each branch, and they pass after the move.
