Skill Tracer
Skills › Superpowers › systematic-debugging › condition-based-waiting.md

condition-based-waiting.md

115 lines · skills/systematic-debugging/condition-based-waiting.md at 2ce8aa1

Open on GitHub
1# Condition-Based Waiting
2

Condition-Based Waiting

Raw lines 1 to 2
3## Overview
4
5Flaky 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.
6
7**Core principle:** Wait for the actual condition you care about, not a guess about how long it takes.
8

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.

Raw lines 3 to 8
9## When to Use
11```dot
12digraph when_to_use {
13 "Test uses setTimeout/sleep?" [shape=diamond];
14 "Testing timing behavior?" [shape=diamond];
15 "Document WHY timeout needed" [shape=box];
16 "Use condition-based waiting" [shape=box];
18 "Test uses setTimeout/sleep?" -> "Testing timing behavior?" [label="yes"];
19 "Testing timing behavior?" -> "Document WHY timeout needed" [label="yes"];
20 "Testing timing behavior?" -> "Use condition-based waiting" [label="no"];
22```
24**Use when:**
25- Tests have arbitrary delays (`setTimeout`, `sleep`, `time.sleep()`)
26- Tests are flaky (pass sometimes, fail under load)
27- Tests timeout when run in parallel
28- Waiting for async operations to complete
30**Don't use when:**
31- Testing actual timing behavior (debounce, throttle intervals)
32- Always document WHY if using arbitrary timeout

When to Use

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
Raw lines 9 to 33
34## Core Pattern
36```typescript
37// ❌ BEFORE: Guessing at timing
38await new Promise(r => setTimeout(r, 50));
39const result = getResult();
40expect(result).toBeDefined();
42// ✅ AFTER: Waiting for condition
43await waitFor(() => getResult() !== undefined);
44const result = getResult();
45expect(result).toBeDefined();
46```

Core Pattern

// ❌ 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();
Raw lines 34 to 47
48## Quick Patterns
50| Scenario | Pattern |
51|----------|---------|
52| Wait for event | `waitFor(() => events.find(e => e.type === 'DONE'))` |
53| Wait for state | `waitFor(() => machine.state === 'ready')` |
54| Wait for count | `waitFor(() => items.length >= 5)` |
55| Wait for file | `waitFor(() => fs.existsSync(path))` |
56| Complex condition | `waitFor(() => obj.ready && obj.value > 10)` |

Quick Patterns

ScenarioPattern
Wait for eventwaitFor(() => events.find(e => e.type === 'DONE'))
Wait for statewaitFor(() => machine.state === 'ready')
Wait for countwaitFor(() => items.length >= 5)
Wait for filewaitFor(() => fs.existsSync(path))
Complex conditionwaitFor(() => obj.ready && obj.value > 10)
Raw lines 48 to 57
58## Implementation
60Generic polling function:
61```typescript
62async function waitFor<T>(
63 condition: () => T | undefined | null | false,
64 description: string,
65 timeoutMs = 5000
66): Promise<T> {
67 const startTime = Date.now();
69 while (true) {
70 const result = condition();
71 if (result) return result;
73 if (Date.now() - startTime > timeoutMs) {
74 throw new Error(`Timeout waiting for ${description} after ${timeoutMs}ms`);
75 }
77 await new Promise(r => setTimeout(r, 10)); // Poll every 10ms
78 }
80```
82See `condition-based-waiting-example.ts` in this directory for complete implementation with domain-specific helpers (`waitForEvent`, `waitForEventCount`, `waitForEventMatch`) from actual debugging session.

Implementation

Generic polling function:

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 condition-based-waiting-example.ts in this directory for complete implementation with domain-specific helpers (waitForEvent, waitForEventCount, waitForEventMatch) from actual debugging session.

Raw lines 58 to 83
84## Common Mistakes
86**❌ Polling too fast:** `setTimeout(check, 1)` - wastes CPU
87**✅ Fix:** Poll every 10ms
89**❌ No timeout:** Loop forever if condition never met
90**✅ Fix:** Always include timeout with clear error
92**❌ Stale data:** Cache state before loop
93**✅ Fix:** Call getter inside loop for fresh data

Common Mistakes

❌ Polling too fast: setTimeout(check, 1) - wastes CPU ✅ Fix: Poll every 10ms

❌ No timeout: Loop forever if condition never met ✅ Fix: Always include timeout with clear error

❌ Stale data: Cache state before loop ✅ Fix: Call getter inside loop for fresh data

Raw lines 84 to 94
95## When Arbitrary Timeout IS Correct
97```typescript
98// Tool ticks every 100ms - need 2 ticks to verify partial output
99await waitForEvent(manager, 'TOOL_STARTED'); // First: wait for condition
100await new Promise(r => setTimeout(r, 200)); // Then: wait for timed behavior
101// 200ms = 2 ticks at 100ms intervals - documented and justified
102```
104**Requirements:**
1051. First wait for triggering condition
1062. Based on known timing (not guessing)
1073. Comment explaining WHY

When Arbitrary Timeout IS Correct

// Tool ticks every 100ms - need 2 ticks to verify partial output
await waitForEvent(manager, 'TOOL_STARTED'); // First: wait for condition
await new Promise(r => setTimeout(r, 200));   // Then: wait for timed behavior
// 200ms = 2 ticks at 100ms intervals - documented and justified

Requirements:

  1. First wait for triggering condition
  2. Based on known timing (not guessing)
  3. Comment explaining WHY
Raw lines 95 to 108
109## Real-World Impact
111From debugging session (2025-10-03):
112- Fixed 15 flaky tests across 3 files
113- Pass rate: 60% → 100%
114- Execution time: 40% faster
115- No more race conditions

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
Raw lines 109 to 115