# Consolidate Conditional Expression

> Merge a run of separate checks that all lead to the same result into one condition with a name that states the shared rule.

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

## Problem

A function has several `if` statements in a row, and every one of them returns the same value or performs the same action. The checks look independent, so a reader cannot tell they express one rule, and a new check gets added in the wrong place.

## Solution

**Combine the checks into a single boolean expression with `||` (or `&&` for nested checks), then extract that expression into a function named for the rule.**

## Before

```ts
function canIssueRefund(order: Order, now: Date): boolean {
  if (order.paymentMethod === "gift-card") return false;
  if (order.refundedAt !== null) return false;
  if (now.getTime() - order.deliveredAt.getTime() > 30 * DAY_MS) return false;
  if (order.items.every((item) => item.finalSale)) return false;

  return order.total > 0;
}
```

## After

```ts
function canIssueRefund(order: Order, now: Date): boolean {
  if (isRefundIneligible(order, now)) return false;
  return order.total > 0;
}

function isRefundIneligible(order: Order, now: Date): boolean {
  const outsideWindow = now.getTime() - order.deliveredAt.getTime() > 30 * DAY_MS;
  return (
    order.paymentMethod === "gift-card" ||
    order.refundedAt !== null ||
    outsideWindow ||
    order.items.every((item) => item.finalSale)
  );
}
```

## Why

- The list of disqualifiers now lives in one place with one name, so adding a fifth is obvious.
- The calling function reads as policy: ineligible orders get `false`.
- The consolidated predicate is a natural unit to test with a table of cases.

## Steps

1. Confirm every check in the run has no side effects and produces the identical outcome. If any check logs, mutates or returns a different value, stop.
2. Join sequential checks with `||`, preserving their original order. For nested `if`s with one outcome, join with `&&`. Run the tests.
3. Extract the combined expression into a function with [Extract Method](https://aipatterns.dev/refactoring/techniques/extract-method.md). Run the tests.
4. Rename the function until it describes the rule without restating the clauses.

## Watch out

- Do not consolidate checks that are truly independent concerns that happen to share a return value today, such as a permissions check and a validation check. They will diverge, and you will split them again.
- Keep clause order. `||` short-circuits left to right, and a later clause may depend on an earlier one guarding against `null`.
- If one check needs its own error message or log line, it is not the same outcome. Leave it separate.
- The rough inverse is [Decompose Conditional](https://aipatterns.dev/refactoring/techniques/decompose-conditional.md) applied to a single large condition; use both together, consolidate first, then name.

## Related

- Fixes [Duplicate Code](https://aipatterns.dev/refactoring/smells/duplicate-code.md) in the form of repeated outcomes, and shortens [Long Method](https://aipatterns.dev/refactoring/smells/long-method.md).
- Often followed by [Decompose Conditional](https://aipatterns.dev/refactoring/techniques/decompose-conditional.md) or [Replace Nested Conditional with Guard Clauses](https://aipatterns.dev/refactoring/techniques/replace-nested-conditional-with-guard-clauses.md).

## Agent checklist

- [ ] Every merged check returns or does exactly the same thing.
- [ ] No merged clause has side effects.
- [ ] Clause order is preserved so null guards still protect later clauses.
- [ ] The extracted predicate's name states the rule, not the list of checks.
