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.

LeetCode Problem 2621

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

  1. Create a new Promise object that takes a resolve function as its argument. This resolve function will be called when the sleep duration is over.
  2. Inside the promise executor, call setTimeout with two arguments: a callback function and millis. The callback function should call resolve().
  3. Return the promise from the sleep function. This allows the caller to await the promise or attach a .then() handler to perform actions after the delay.
  4. When setTimeout triggers after approximately millis milliseconds, it invokes the callback, which in turn calls resolve(), 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.