LeetCode 2621 - Sleep
The problem asks us to implement an asynchronous function sleep that pauses execution for a given number of milliseconds, specified by the input millis.
Difficulty: 🟢 Easy
Topics: —
Solution
Problem Understanding
The problem asks us to implement an asynchronous function sleep that pauses execution for a given number of milliseconds, specified by the input millis. In other words, when sleep(millis) is called, the function should return a promise that resolves after approximately millis milliseconds have passed. The resolved value can be any value, as the problem does not enforce a specific return.
The input, millis, is guaranteed to be a positive integer between 1 and 1000. This constraint indicates that the maximum sleep time is relatively small (1 second), so efficiency is not a concern. The problem also allows for minor deviations from the exact timing, acknowledging that JavaScript timers may not be precise down to the millisecond.
Edge cases include the minimum value millis = 1, which tests very short sleeps, and the maximum millis = 1000, which tests longer sleeps within the allowed range. The problem guarantees that millis is always within these bounds, so we do not need to handle negative values, zero, or non-integer inputs.
In short, the problem is about creating a non-blocking, asynchronous delay using the provided milliseconds.
Approaches
A brute-force approach would be to create a busy-wait loop that blocks execution until the specified time has passed. For example, one could repeatedly check the current timestamp and continue looping until the difference reaches millis. While this approach technically works, it is inefficient because it blocks the main thread and wastes CPU cycles, defeating the purpose of asynchronous programming.
The optimal approach leverages the asynchronous capabilities of JavaScript (or similar languages) using promises and timers. By using setTimeout, we can schedule a function to resolve a promise after millis milliseconds. This is non-blocking, efficient, and leverages the event loop for accurate asynchronous behavior.
| Approach | Time Complexity | Space Complexity | Notes |
|---|---|---|---|
| Brute Force (Busy-wait) | O(millis) | O(1) | Blocks the main thread, inefficient |
| Optimal (Promise + setTimeout) | O(1) | O(1) | Non-blocking, efficient, uses event loop |
Algorithm Walkthrough
- Create a new
Promiseobject that takes aresolvefunction as its argument. Thisresolvefunction will be called when the sleep duration is over. - Inside the promise executor, call
setTimeoutwith two arguments: a callback function andmillis. The callback function should callresolve(). - Return the promise from the
sleepfunction. This allows the caller toawaitthe promise or attach a.then()handler to perform actions after the delay. - When
setTimeouttriggers after approximatelymillismilliseconds, it invokes the callback, which in turn callsresolve(), completing the promise and allowing the asynchronous flow to continue.
Why it works: The key property is that setTimeout schedules the callback asynchronously, ensuring that the code after sleep does not execute until the timer finishes. This guarantees the behavior matches the intended delay while keeping the thread non-blocking.
Python Solution
import asyncio
async def sleep(millis: int) -> int:
await asyncio.sleep(millis / 1000)
return millis
In this implementation, we use Python's asyncio.sleep, which takes a duration in seconds, so we divide millis by 1000. The function is marked as async, allowing it to be awaited. After the sleep duration, we return the original millis value, matching the expected output.
Go Solution
import (
"time"
)
func Sleep(millis int) int {
time.Sleep(time.Duration(millis) * time.Millisecond)
return millis
}
In Go, time.Sleep takes a time.Duration, which can represent milliseconds. We multiply millis by time.Millisecond to convert it to the correct unit. The function is synchronous in Go but effectively blocks for the specified duration, which is acceptable for this problem.
Worked Examples
Example 1: millis = 100
| Step | Action | Notes |
|---|---|---|
| 1 | Call sleep(100) |
Promise is created |
| 2 | setTimeout schedules callback in 100 ms |
Non-blocking |
| 3 | After ~100 ms, callback executes | resolve() called |
| 4 | Promise resolves, .then() executes |
Output: 100 |
Example 2: millis = 200
| Step | Action | Notes |
|---|---|---|
| 1 | Call sleep(200) |
Promise is created |
| 2 | setTimeout schedules callback in 200 ms |
Non-blocking |
| 3 | After ~200 ms, callback executes | resolve() called |
| 4 | Promise resolves, .then() executes |
Output: 200 |
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | O(1) | Creating a promise and scheduling a timer is constant time; the actual wait does not count as active computation. |
| Space | O(1) | Only a single promise and timer are allocated; no extra data structures are needed. |
The complexity reflects that the function does not perform loops or recursive calls proportional to millis. The actual sleeping time does not affect complexity since it is asynchronous.
Test Cases
import asyncio
import time
# Provided examples
assert asyncio.run(sleep(100)) == 100 # should resolve after ~100ms
assert asyncio.run(sleep(200)) == 200 # should resolve after ~200ms
# Boundary tests
assert asyncio.run(sleep(1)) == 1 # minimum sleep
assert asyncio.run(sleep(1000)) == 1000 # maximum sleep
# Additional tests
start = time.time()
assert asyncio.run(sleep(50)) == 50
assert abs((time.time() - start) * 1000 - 50) < 10 # check approximate timing
start = time.time()
assert asyncio.run(sleep(500)) == 500
assert abs((time.time() - start) * 1000 - 500) < 10 # check approximate timing
| Test | Why |
|---|---|
| 100 ms | basic functionality test |
| 200 ms | basic functionality test |
| 1 ms | tests minimum boundary |
| 1000 ms | tests maximum boundary |
| 50 ms | tests short sleep and approximate timing |
| 500 ms | tests longer sleep and timing accuracy |
Edge Cases
The first edge case is millis = 1, the smallest possible sleep duration. This tests that the function handles very short waits without error or rounding issues. The implementation converts milliseconds to seconds correctly and schedules the timer accurately.
The second edge case is millis = 1000, the largest allowed sleep. This ensures the function can handle the upper boundary without integer overflow or timer inaccuracies.
A third edge case involves timing accuracy. Since minor deviations are allowed, we must verify that the function does not throw errors or behave unpredictably when the actual sleep time differs slightly from millis. Both Python's asyncio.sleep and Go's time.Sleep handle these minor deviations gracefully.
This solution correctly handles all the above cases while remaining simple, efficient, and fully asynchronous.