Don't wrap in production
slowdep adds artificial latency. In production, this means real users wait longer for no benefit. Always gate the wrap on an environment check.
import { withLatency } from 'slowdep' // The conditional wrap pattern — zero cost in production const queryUser = process.env.NODE_ENV !== 'production' ? withLatency(dbQueryUser, 'postgres') : dbQueryUser
For a reusable pattern that centralizes the check, see Production usage which covers a maybeSlowDep helper and tree-shaking options.
Wrap at the boundary
Wrap the function that directly calls the external service — not a function several layers up the call stack. Wrapping too high obscures which dependency is slow and makes it impossible to give different services different latency profiles.
Wrapping processOrder mixes together database, payment, and email latency into one opaque number. You can't tune them individually.
// Too high: processOrder calls db + stripe + email internally const slowProcessOrder = withLatency(processOrder, 'http')
Wrap each client or adapter function with the preset that matches the real service.
// At the boundary: each dep gets its own realistic profile const slowQuery = withLatency(db.query, 'postgres') const slowCharge = withLatency(stripe.charge, 'stripe') const slowEmail = withLatency(mailer.send, 'http')
Test the failure path
The most valuable thing slowdep does is let you test code paths that are hard to trigger against a real service. Set errorRate high temporarily — even to 0.5 — to force your retry and fallback logic to execute on every test run.
// In a specific resilience test: set errorRate to 50% // Every other call fails — retry logic runs every time const flakyCharge = withLatency(createCharge, { p50: 200, p99: 2000, errorRate: 0.5 }) test('retries and eventually succeeds', async () => { const result = await withRetry(flakyCharge, { maxAttempts: 5 }) expect(result).toBeDefined() })
Use a high error rate only in tests specifically targeting resilience. Leave it at the preset default (or lower) for integration tests that are testing normal behavior.
Use realistic presets for integration tests
Zero-latency tests (no slowdep, or slowdep mocked away) catch logic bugs. Realistic-latency integration tests catch timeout, retry, and concurrency bugs that zero-latency tests never trigger.
Consider a two-tier test setup:
- Unit tests: Mock
withLatencyaway entirely. Fast, deterministic, good for logic coverage. - Integration tests: Enable slowdep with realistic presets. Slower, but catches timeout misconfiguration, insufficient retry budgets, and cascade failure bugs before they reach production.
// jest.config.integration.js export default { testMatch: ['**/*.integration.test.ts'], testEnvironmentOptions: { // No SLOWDEP_BYPASS — latency is active env: { NODE_ENV: 'test' } }, testTimeout: 30000 // higher timeout for realistic latency }
Don't use slowdep as a load testing tool
slowdep delays individual calls, one at a time. It is not a load generator, a traffic simulation tool, or a concurrency stress tester. It answers the question "what happens when a single call takes longer than expected?" — not "what happens when 1,000 users hit my service simultaneously?"
For load testing and concurrency simulation, use dedicated tools like k6, Artillery, or wrk. slowdep and load testing are complementary: use slowdep to validate your resilience patterns are correct, then use a load tester to validate they scale.
slowdep answers: "Does my code handle a slow dependency correctly?" Load testers answer: "Does my system handle many concurrent users?" Use both.
Pick p99 carefully
The 99th percentile is the boundary between "normal variance" and "something is wrong." If your timeout is shorter than the p99 of a dependency, 1% of calls will time out even during normal operation. In a service processing 100 requests per second, that's one timeout per second — a constant stream of errors.
// Bad: timeout of 100ms with postgres p99 of 200ms // 1% of calls will time out in normal operation const QUERY_TIMEOUT = 100 // too short const slowQuery = withLatency(query, 'postgres') // p99: 200ms // Better: timeout >= 3-5× p99 to account for the tail const QUERY_TIMEOUT = 1000 // comfortable above p99
The rule of thumb: set your timeout to at least 3–5× the p99 of the dependency. For databases (p99 ~200ms), a 1s timeout is reasonable. For LLM APIs (p99 ~7–8s), you need 30s or more. slowdep surfaces the mismatch during testing rather than production.