# Introduce Assertion

> State an assumption the code already relies on as an explicit check that fails fast, instead of leaving it in a comment or in the reader's head.

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

## Problem

A block of code only works if some condition holds: a list is non-empty, a total is non-negative, a field was set earlier. Nothing checks it. When the assumption breaks, the code fails later and somewhere else, or silently produces wrong numbers.

## Solution

**Add an assertion at the point where the assumption must be true.** It documents the requirement and stops execution with a clear message when it is violated.

## Before

```ts
function allocatePayment(payment: Payment, invoices: Invoice[]): Allocation[] {
  // invoices are sorted oldest first and none are already paid
  const allocations: Allocation[] = [];
  let remaining = payment.amountCents;
  for (const inv of invoices) {
    const applied = Math.min(remaining, inv.balanceCents);
    allocations.push({ invoiceId: inv.id, amountCents: applied });
    remaining -= applied;
    if (remaining === 0) break;
  }
  return allocations;
}
```

## After

```ts
function assert(condition: unknown, message: string): asserts condition {
  if (!condition) throw new Error(`Invariant failed: ${message}`);
}

function allocatePayment(payment: Payment, invoices: Invoice[]): Allocation[] {
  assert(invoices.every((inv) => inv.balanceCents > 0), "invoices must be unpaid");
  assert(
    invoices.every((inv, i) => i === 0 || invoices[i - 1].dueDate <= inv.dueDate),
    "invoices must be sorted by due date",
  );
  const allocations: Allocation[] = [];
  let remaining = payment.amountCents;
  for (const inv of invoices) {
    const applied = Math.min(remaining, inv.balanceCents);
    allocations.push({ invoiceId: inv.id, amountCents: applied });
    remaining -= applied;
    if (remaining === 0) break;
  }
  return allocations;
}
```

## Why

- The comment becomes executable and cannot go stale.
- A violation fails at its source with a message that names the broken rule.
- TypeScript's `asserts condition` signature also narrows types after the call.

## Steps

1. Find an assumption stated in a comment, in a name, or implied by the code (division by a count, `[0]` access, non-null `!`).
2. Write the assertion right where the assumption is first needed. Make the message name the rule. Run the tests; they should still pass because the assumption held.
3. If a test now fails, you found a real bug or a wrong assumption. Fix one of them before going on.
4. Delete the comment the assertion replaced.

## Watch out

- Assertions are for programmer errors, not for input validation. User input, API payloads and file contents can legitimately be wrong; validate them and return errors or throw domain exceptions.
- Do not assert things the type system already guarantees.
- Keep the check cheap, or guard it behind a debug flag. The sort check above is O(n) on a hot path; drop it in production if profiling shows a cost.
- Some runtimes strip assertions in production (`python -O`, Java without `-ea`). Do not put side effects inside them, and do not rely on them for security.
- Removing an assertion has no named technique. Remove it only when the assumption is no longer needed.

## Related

- Replaces explanatory [Comments](https://aipatterns.dev/refactoring/smells/comments.md).
- When absence is a valid state rather than a broken assumption, use [Introduce Null Object](https://aipatterns.dev/refactoring/techniques/introduce-null-object.md).
- For expected failure cases, see [Replace Error Code with Exception](https://aipatterns.dev/refactoring/techniques/replace-error-code-with-exception.md).

## Agent checklist

- [ ] The asserted condition is an internal invariant, not untrusted input.
- [ ] The assertion has no side effects.
- [ ] Its message names the rule that was broken.
- [ ] Existing tests still pass, proving the assumption held.
