---
name: Condition-Based Waiting
description: Replace arbitrary timeouts with condition polling for reliable async tests
when_to_use: when tests have race conditions, timing dependencies, or inconsistent pass/fail behavior
version: 1.1.0
languages: all
progressive_disclosure:
  entry_point:
    summary: "Replace arbitrary timeouts with condition polling for reliable async tests"
    when_to_use: "Tests with setTimeout/sleep, flaky tests, or timing-dependent async operations"
    quick_start: |
      1. Identify arbitrary delays in tests (setTimeout, sleep, time.sleep())
      2. Replace with condition-based waiting (waitFor pattern)
      3. Use domain-specific helpers for common scenarios
      See @example.ts for complete working implementation
    core_pattern: |
      // ❌ Guessing at timing
      await new Promise(r => setTimeout(r, 50));

      // ✅ Waiting for condition
      await waitFor(() => getResult() !== undefined);
  references:
    - path: references/patterns-and-implementation.md
      purpose: Detailed waiting patterns, implementation guide, and common mistakes
      when_to_read: When implementing waitFor or debugging timing issues
effort: medium
---

# Condition-Based Waiting

## Overview

Flaky tests often guess at timing with arbitrary delays. This creates race conditions where tests pass on fast machines but fail under load or in CI.

**Core principle:** Wait for the actual condition you care about, not a guess about how long it takes.

## When to Use

```dot
digraph when_to_use {
    "Test uses setTimeout/sleep?" [shape=diamond];
    "Testing timing behavior?" [shape=diamond];
    "Document WHY timeout needed" [shape=box];
    "Use condition-based waiting" [shape=box];

    "Test uses setTimeout/sleep?" -> "Testing timing behavior?" [label="yes"];
    "Testing timing behavior?" -> "Document WHY timeout needed" [label="yes"];
    "Testing timing behavior?" -> "Use condition-based waiting" [label="no"];
}
```

**Use when:**
- Tests have arbitrary delays (`setTimeout`, `sleep`, `time.sleep()`)
- Tests are flaky (pass sometimes, fail under load)
- Tests timeout when run in parallel
- Waiting for async operations to complete

**Don't use when:**
- Testing actual timing behavior (debounce, throttle intervals)
- Always document WHY if using arbitrary timeout

## Core Pattern

```typescript
// ❌ BEFORE: Guessing at timing
await new Promise(r => setTimeout(r, 50));
const result = getResult();
expect(result).toBeDefined();

// ✅ AFTER: Waiting for condition
await waitFor(() => getResult() !== undefined);
const result = getResult();
expect(result).toBeDefined();
```

## Quick Patterns

| Scenario | Pattern |
|----------|---------|
| Wait for event | `waitFor(() => events.find(e => e.type === 'DONE'))` |
| Wait for state | `waitFor(() => machine.state === 'ready')` |
| Wait for count | `waitFor(() => items.length >= 5)` |
| Wait for file | `waitFor(() => fs.existsSync(path))` |
| Complex condition | `waitFor(() => obj.ready && obj.value > 10)` |

## Implementation

Generic polling function:
```typescript
async function waitFor<T>(
  condition: () => T | undefined | null | false,
  description: string,
  timeoutMs = 5000
): Promise<T> {
  const startTime = Date.now();

  while (true) {
    const result = condition();
    if (result) return result;

    if (Date.now() - startTime > timeoutMs) {
      throw new Error(`Timeout waiting for ${description} after ${timeoutMs}ms`);
    }

    await new Promise(r => setTimeout(r, 10)); // Poll every 10ms
  }
}
```

See @example.ts for complete implementation with domain-specific helpers (`waitForEvent`, `waitForEventCount`, `waitForEventMatch`).

For detailed patterns, implementation guide, and common mistakes, see @references/patterns-and-implementation.md

## Real-World Impact

From debugging session (2025-10-03):
- Fixed 15 flaky tests across 3 files
- Pass rate: 60% → 100%
- Execution time: 40% faster
- No more race conditions
