JavaScript has no built-in sleep — you can't pause execution synchronously the way time.sleep(2) works in Python. The async-friendly equivalent is to return a Promise that resolves after a delay, then await it. That's what you're building.
Implement sleep(ms). It takes a number of milliseconds and returns a Promise that resolves (with undefined) after at least ms milliseconds have elapsed. The function itself returns immediately — only the await pauses your code.
function sleep(ms) {
// returns a Promise<void> that resolves after `ms` milliseconds
}
// Basic usage with await
async function demo() {
console.log('start');
await sleep(500);
console.log('half a second later');
}
demo();
// Pure-promise usage (no async/await)
sleep(200).then(() => console.log('done'));
// Resolves with undefined
sleep(50).then((value) => {
console.log(value); // undefined
});
sleep(0) must not block. It still returns a Promise that resolves on a later turn of the event loop.undefined. Nothing useful is passed in.setTimeout — no busy-loops, no Date.now() polling. Spin-waiting freezes the browser tab.ms — setTimeout itself treats negative values as 0. You don't need to add extra validation; lean on the platform.AbortSignal. Once you call sleep, the timer runs to completion.You'll wrap setTimeout in a Promise so the rest of your code can await a delay without freezing the page.
Other languages have a one-liner like time.sleep(2) that pauses the program for two seconds. JavaScript doesn't — and you wouldn't want one, because pausing JavaScript pauses the whole browser tab. The async-friendly substitute is a Promise that resolves after a delay. You hand the wait off to the platform's timer, get a Promise back immediately, and await it inside an async function. Your async function suspends; the rest of the page keeps running.
Think of sleep(ms) as a receipt for a future event. You hand the timer your ms, it hands you back a Promise. The function call itself completes in microseconds. Somewhere down the road — at least ms milliseconds later — the timer fires and the Promise resolves. Whatever was awaiting it picks up the value (which is just undefined).
If you came from another language, the obvious thing to reach for is a loop that watches the clock:
function sleepBroken(ms) {
const start = Date.now();
while (Date.now() - start < ms) {
// spin until enough time has passed
}
}
This does pause for ms milliseconds. But it pauses by pinning the CPU at 100% — the JavaScript thread is busy doing the loop, so nothing else can run. No timers fire, no clicks process, no animations advance. For 500ms the browser tab looks frozen, and on a long sleep the OS may put up a "page unresponsive" dialog. You also can't return a Promise from this; callers can't await it.
The fix is one line. Return a Promise whose executor schedules resolve with setTimeout:
function sleep(ms) {
// `new Promise(executor)` runs `executor` synchronously and returns a pending Promise.
// We don't capture the returned timer id — sleep has no cancellation in this version.
return new Promise((resolve) => setTimeout(resolve, ms));
}
module.exports = { sleep };
Three things to notice. First, new Promise(executor) runs its executor function right away — but the Promise it returns is pending, not resolved. That's exactly what we want: hand the caller a "watch this for later" handle. Second, setTimeout(resolve, ms) registers a timer with the platform; control returns immediately. When the timer fires, the platform calls resolve() — no arguments, so the Promise fulfills with undefined. Third, we pass resolve itself as the callback rather than wrapping it in () => resolve(). Both work; the unwrapped form is shorter and any extra argument setTimeout might pass is harmless because Promise.resolve only looks at its first argument.
About the edge cases the description calls out. setTimeout already clamps negative delays to 0, so sleep(-100) is the same as sleep(0) — you don't need extra validation. And even sleep(0) is still asynchronous: setTimeout(fn, 0) schedules fn for a future turn of the event loop, never the current one.
Trace await sleep(500) inside an async function demo().
sleep(500) runs. new Promise(executor) calls the executor synchronously; inside, setTimeout(resolve, 500) registers a timer with the host and returns. sleep hands back a pending Promise. About a microsecond of work — no waiting yet.demo, await sees a pending Promise. It suspends demo and returns control to whoever called demo. The main thread is now free; clicks, other timers, animations all run normally.setTimeout is a minimum, not an exact guarantee), the timer fires. The host calls resolve() with no arguments. The Promise transitions from pending to fulfilled, with value undefined.demo. When it runs, demo picks up after the await. The expression await sleep(500) evaluates to undefined, which you typically ignore.await — sleep(500); doNextThing(); runs doNextThing immediately. The Promise resolves 500ms later into nothing, because nobody is listening. Always either await sleep(500) or sleep(500).then(...) if you actually want the delay.await is only legal inside an async function (or at the top level of an ES module). Writing function run() { await sleep(100); doNextThing(); } is a syntax error. If you can't make the caller async, fall back to sleep(100).then(doNextThing) — the chained callback is the only place where "after the delay" code can safely live.sleep(0) as synchronous — sleep(0) still yields control: the Promise resolves on a later turn of the event loop, never in the same synchronous run. Code like let x; sleep(0).then(() => x = 1); console.log(x); logs undefined, not 1.setTimeout is a floor, not a precise schedule. You code sleep(100) expecting ~100ms, but if the tab is in the background, browsers throttle timers to fire roughly once per second — your 100ms wait stretches to 1000ms+. Even in the foreground, busy work on the main thread can delay the callback by a few ms. Tests must allow jitter (e.g. expect(elapsed).toBeGreaterThanOrEqual(90) rather than toBe(100)), and production code should never branch on exact sleep duration.setTimeout keeps the Node process alive and pins the closure (and anything it captures) in memory until it fires. A sleep(60_000) you forgot to cancel will keep a script from exiting for a full minute. In Node specifically, setTimeout returns a Timeout object you can .unref() if you don't want it to block process exit; this naive sleep doesn't expose it, which is fine for most code but worth knowing.Keep practising the same patterns with a nearby challenge.
No submissions yet
Share your approach and start the discussion.
JavaScript has no built-in sleep — you can't pause execution synchronously the way time.sleep(2) works in Python. The async-friendly equivalent is to return a Promise that resolves after a delay, then await it. That's what you're building.
Implement sleep(ms). It takes a number of milliseconds and returns a Promise that resolves (with undefined) after at least ms milliseconds have elapsed. The function itself returns immediately — only the await pauses your code.
function sleep(ms) {
// returns a Promise<void> that resolves after `ms` milliseconds
}
// Basic usage with await
async function demo() {
console.log('start');
await sleep(500);
console.log('half a second later');
}
demo();
// Pure-promise usage (no async/await)
sleep(200).then(() => console.log('done'));
// Resolves with undefined
sleep(50).then((value) => {
console.log(value); // undefined
});
sleep(0) must not block. It still returns a Promise that resolves on a later turn of the event loop.undefined. Nothing useful is passed in.setTimeout — no busy-loops, no Date.now() polling. Spin-waiting freezes the browser tab.ms — setTimeout itself treats negative values as 0. You don't need to add extra validation; lean on the platform.AbortSignal. Once you call sleep, the timer runs to completion.You'll wrap setTimeout in a Promise so the rest of your code can await a delay without freezing the page.
Other languages have a one-liner like time.sleep(2) that pauses the program for two seconds. JavaScript doesn't — and you wouldn't want one, because pausing JavaScript pauses the whole browser tab. The async-friendly substitute is a Promise that resolves after a delay. You hand the wait off to the platform's timer, get a Promise back immediately, and await it inside an async function. Your async function suspends; the rest of the page keeps running.
Think of sleep(ms) as a receipt for a future event. You hand the timer your ms, it hands you back a Promise. The function call itself completes in microseconds. Somewhere down the road — at least ms milliseconds later — the timer fires and the Promise resolves. Whatever was awaiting it picks up the value (which is just undefined).
If you came from another language, the obvious thing to reach for is a loop that watches the clock:
function sleepBroken(ms) {
const start = Date.now();
while (Date.now() - start < ms) {
// spin until enough time has passed
}
}
This does pause for ms milliseconds. But it pauses by pinning the CPU at 100% — the JavaScript thread is busy doing the loop, so nothing else can run. No timers fire, no clicks process, no animations advance. For 500ms the browser tab looks frozen, and on a long sleep the OS may put up a "page unresponsive" dialog. You also can't return a Promise from this; callers can't await it.
The fix is one line. Return a Promise whose executor schedules resolve with setTimeout:
function sleep(ms) {
// `new Promise(executor)` runs `executor` synchronously and returns a pending Promise.
// We don't capture the returned timer id — sleep has no cancellation in this version.
return new Promise((resolve) => setTimeout(resolve, ms));
}
module.exports = { sleep };
Three things to notice. First, new Promise(executor) runs its executor function right away — but the Promise it returns is pending, not resolved. That's exactly what we want: hand the caller a "watch this for later" handle. Second, setTimeout(resolve, ms) registers a timer with the platform; control returns immediately. When the timer fires, the platform calls resolve() — no arguments, so the Promise fulfills with undefined. Third, we pass resolve itself as the callback rather than wrapping it in () => resolve(). Both work; the unwrapped form is shorter and any extra argument setTimeout might pass is harmless because Promise.resolve only looks at its first argument.
About the edge cases the description calls out. setTimeout already clamps negative delays to 0, so sleep(-100) is the same as sleep(0) — you don't need extra validation. And even sleep(0) is still asynchronous: setTimeout(fn, 0) schedules fn for a future turn of the event loop, never the current one.
Trace await sleep(500) inside an async function demo().
sleep(500) runs. new Promise(executor) calls the executor synchronously; inside, setTimeout(resolve, 500) registers a timer with the host and returns. sleep hands back a pending Promise. About a microsecond of work — no waiting yet.demo, await sees a pending Promise. It suspends demo and returns control to whoever called demo. The main thread is now free; clicks, other timers, animations all run normally.setTimeout is a minimum, not an exact guarantee), the timer fires. The host calls resolve() with no arguments. The Promise transitions from pending to fulfilled, with value undefined.demo. When it runs, demo picks up after the await. The expression await sleep(500) evaluates to undefined, which you typically ignore.await — sleep(500); doNextThing(); runs doNextThing immediately. The Promise resolves 500ms later into nothing, because nobody is listening. Always either await sleep(500) or sleep(500).then(...) if you actually want the delay.await is only legal inside an async function (or at the top level of an ES module). Writing function run() { await sleep(100); doNextThing(); } is a syntax error. If you can't make the caller async, fall back to sleep(100).then(doNextThing) — the chained callback is the only place where "after the delay" code can safely live.sleep(0) as synchronous — sleep(0) still yields control: the Promise resolves on a later turn of the event loop, never in the same synchronous run. Code like let x; sleep(0).then(() => x = 1); console.log(x); logs undefined, not 1.setTimeout is a floor, not a precise schedule. You code sleep(100) expecting ~100ms, but if the tab is in the background, browsers throttle timers to fire roughly once per second — your 100ms wait stretches to 1000ms+. Even in the foreground, busy work on the main thread can delay the callback by a few ms. Tests must allow jitter (e.g. expect(elapsed).toBeGreaterThanOrEqual(90) rather than toBe(100)), and production code should never branch on exact sleep duration.setTimeout keeps the Node process alive and pins the closure (and anything it captures) in memory until it fires. A sleep(60_000) you forgot to cancel will keep a script from exiting for a full minute. In Node specifically, setTimeout returns a Timeout object you can .unref() if you don't want it to block process exit; this naive sleep doesn't expose it, which is fine for most code but worth knowing.Keep practising the same patterns with a nearby challenge.
No submissions yet
Share your approach and start the discussion.