Setup

Four steps to realistic latency

1
Install
Add slowdep to your project. Zero dependencies, ships TypeScript types.
npm install slowdep
2
Import
Import withLatency using ESM or CommonJS — both work out of the box.
import { withLatency } from 'slowdep'
const { withLatency } = require('slowdep')
3
Wrap a function with a preset
Pass your async function and a preset name. The wrapped function has the same signature as the original.
// Your original async function
async function queryUser(id) {
  return db.query('SELECT * FROM users WHERE id = $1', [id])
}

// Wrapped with Postgres-realistic latency
const slowQueryUser = withLatency(queryUser, 'postgres')
4
Use the wrapped function
Call the wrapped function exactly as you called the original. It returns the same result — just with realistic latency added before execution.
// Use exactly like the original
const user = await slowQueryUser(42)
console.log(user.name) // same result, realistic timing
How it works

What just happened?

When you call slowQueryUser(42), slowdep samples a latency value from a lognormal distribution parameterized by the 'postgres' preset (p50: 5ms, p99: 200ms). It waits that many milliseconds, then calls your original queryUser(42) function and returns its result.

The lognormal distribution is key. Unlike a flat setTimeout, it produces realistic variance: most calls are fast (near 5ms), some take longer (20–50ms), and occasionally one takes 100–200ms. That long tail is where timeout misconfiguration and missing retry logic hide.

If the preset's errorRate fires (postgres: 0.1% of calls), the wrapper rejects with a transient error before calling your function — giving your retry and fallback code something real to handle.

Production tip

Only wrap in development and test environments. Use process.env.NODE_ENV !== 'production' ? withLatency(fn, preset) : fn to ensure zero overhead in production. See Production usage for the full pattern.

Next steps

Where to go from here