# Decompose Conditional

> Move a complex condition and each of its branches into well-named functions so the decision reads as intent instead of arithmetic.

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

## Problem

An `if` statement has a condition built from several comparisons, and each branch holds a block of calculation. You have to evaluate the boolean logic in your head to learn *why* the branch exists, and the branches are too long to see both at once.

## Solution

**Extract the condition into a function named for the question it answers, and extract each branch into a function named for what it does.** The `if` statement stays, but it now reads as one line of intent per part.

## Before

```ts
function lateFee(invoice: Invoice, today: Date): number {
  let fee: number;
  if (
    today.getTime() > invoice.dueDate.getTime() + 3 * DAY_MS &&
    invoice.status !== "disputed" &&
    !invoice.customer.feeWaiver
  ) {
    const daysLate = Math.floor((today.getTime() - invoice.dueDate.getTime()) / DAY_MS);
    fee = Math.min(invoice.balance * 0.015 * Math.ceil(daysLate / 30), invoice.balance * 0.1);
  } else {
    fee = 0;
  }
  return fee;
}
```

## After

```ts
function lateFee(invoice: Invoice, today: Date): number {
  return isPastGracePeriod(invoice, today) ? accruedPenalty(invoice, today) : 0;
}

function isPastGracePeriod(invoice: Invoice, today: Date): boolean {
  const graceEnds = invoice.dueDate.getTime() + 3 * DAY_MS;
  return today.getTime() > graceEnds && invoice.status !== "disputed" && !invoice.customer.feeWaiver;
}

function accruedPenalty(invoice: Invoice, today: Date): number {
  const daysLate = Math.floor((today.getTime() - invoice.dueDate.getTime()) / DAY_MS);
  const monthsLate = Math.ceil(daysLate / 30);
  return Math.min(invoice.balance * 0.015 * monthsLate, invoice.balance * 0.1);
}
```

## Why

- The condition gets a name, so the reader learns the business rule without decoding three comparisons.
- Each branch can be read, tested and changed on its own.
- The top-level function shrinks to the decision, which is usually what a reviewer wants to check.

## Steps

1. Extract the whole condition into a function that returns `boolean`. Name it for the question, not the mechanics (`isPastGracePeriod`, not `checkDateAndStatus`). Run the tests.
2. Extract the `then` branch into its own function with [Extract Method](https://aipatterns.dev/refactoring/techniques/extract-method.md). Run the tests.
3. Extract the `else` branch if it is more than a trivial value. Run the tests.
4. If both branches now return a value, collapse the `if` into a ternary or early return. Run the tests.

## Watch out

- Do not decompose a condition that is already one clear comparison (`if (cart.isEmpty())`). A wrapper named `cartIsEmpty` adds a jump and no meaning.
- Extracted predicates must stay side-effect free. If the original condition called something that mutates state, keep that call ordering visible.
- Watch short-circuit evaluation. Moving `a && b()` into a function is safe; splitting it into two separately evaluated variables may call `b()` when it was previously skipped.
- If the branches pick between whole *types* of behavior and the same `if` recurs elsewhere, go further with [Replace Conditional with Polymorphism](https://aipatterns.dev/refactoring/techniques/replace-conditional-with-polymorphism.md).
- There is no inverse technique; inline the functions with [Inline Method](https://aipatterns.dev/refactoring/techniques/inline-method.md) if a name turns out to add nothing.

## Related

- Fixes [Long Method](https://aipatterns.dev/refactoring/smells/long-method.md) and replaces explanatory [Comments](https://aipatterns.dev/refactoring/smells/comments.md) with names.
- Built from [Extract Method](https://aipatterns.dev/refactoring/techniques/extract-method.md).
- Pair with [Consolidate Conditional Expression](https://aipatterns.dev/refactoring/techniques/consolidate-conditional-expression.md) when several `if`s share one outcome.
- Follow with [Replace Nested Conditional with Guard Clauses](https://aipatterns.dev/refactoring/techniques/replace-nested-conditional-with-guard-clauses.md) when decomposed branches are nested.

## Agent checklist

- [ ] The condition has at least two clauses or a non-obvious meaning; otherwise leave it inline.
- [ ] Each extracted predicate is pure and named as a yes/no question.
- [ ] Short-circuit order and evaluation count of every call in the condition are unchanged.
- [ ] Tests pass after each extraction, not only at the end.
