2821. Delay the Resolution of Each Promise 🔒
Problem Description
You are given an array of functions called functions
where each function returns a promise, and a number ms
representing a delay duration in milliseconds.
Your task is to create and return a new array of functions where each function:
- Corresponds to a function from the original
functions
array - When called, waits for
ms
milliseconds before executing the original function - Returns a promise that resolves or rejects with the same value as the original function's promise
The key requirements are:
- Each function in the input array returns a promise
- The output array should maintain the same order as the input array
- Each new function should add a delay of
ms
milliseconds before executing its corresponding original function - The delayed promise should preserve the resolution or rejection behavior of the original promise
For example, if you have a function that returns a promise resolving to "hello"
immediately, and ms = 1000
, the new function should wait 1000 milliseconds, then execute the original function, and return a promise that resolves to "hello"
.
The solution uses map()
to transform each function, creating an async wrapper that:
- First waits for
ms
milliseconds usingsetTimeout
wrapped in a Promise - Then calls and returns the result of the original function
Intuition
The core challenge is to add a delay before each function executes while preserving its promise-based behavior. Since we need to transform each function in the array while maintaining order, map()
is the natural choice for iteration.
For adding the delay, we need to wait for a specific amount of time before doing something else. JavaScript's setTimeout
is the standard way to introduce delays, but it uses callbacks. To work with promises and modern async patterns, we wrap setTimeout
in a Promise: new Promise(resolve => setTimeout(resolve, ms))
. This creates a promise that resolves after ms
milliseconds.
Since we need to:
- First wait for the delay
- Then execute the original function
- Return the result as a promise
The async/await pattern fits perfectly. We create an async function that:
- Uses
await
to pause execution for the delay - Then calls the original function and returns its result
The beauty of this approach is that async
functions automatically return promises. When we return fn()
from an async function, it will:
- If
fn()
returns a promise that resolves, our async function's promise resolves with the same value - If
fn()
returns a promise that rejects, our async function's promise rejects with the same error
This natural behavior of async functions means we don't need to manually handle promise resolution or rejection - the async function wrapper automatically preserves the original promise's behavior while adding the delay.
Solution Approach
The implementation uses the map()
method to transform each function in the input array into a new delayed version:
function delayAll(functions: Function[], ms: number): Function[] {
return functions.map(fn => {
return async function () {
await new Promise(resolve => setTimeout(resolve, ms));
return fn();
};
});
}
Let's break down the implementation step by step:
-
Array Transformation with
map()
: We iterate through thefunctions
array usingmap()
, which creates a new array where each element is a transformed version of the original function. -
Creating the Wrapper Function: For each function
fn
in the original array, we return a new async function. This wrapper function is what gets stored in the result array. -
Implementing the Delay: Inside the async function, we create a delay using:
await new Promise(resolve => setTimeout(resolve, ms))
This line creates a Promise that resolves after
ms
milliseconds. Theawait
keyword pauses the function execution until this promise resolves. -
Executing the Original Function: After the delay completes, we call the original function with
return fn()
. Sincefn()
returns a promise, and we're inside an async function, this will:- If
fn()
resolves with a value, the async wrapper will resolve with the same value - If
fn()
rejects with an error, the async wrapper will reject with the same error
- If
-
Return Type: The function returns
Function[]
, an array of the new delayed functions. Each function in this array, when called, will execute its delay-then-execute behavior.
The key pattern here is the async function wrapper, which elegantly handles both the delay requirement and the promise preservation without needing explicit promise chaining or error handling code.
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a concrete example to understand how the solution works.
Input:
functions = [fn1, fn2]
where:fn1
returns a promise that resolves to5
immediatelyfn2
returns a promise that resolves to10
immediately
ms = 100
(delay of 100 milliseconds)
Step 1: Apply map() transformation
The map()
function iterates through each function:
For fn1
:
- Creates a new async function that:
- First waits 100ms using
await new Promise(resolve => setTimeout(resolve, 100))
- Then calls
fn1()
and returns its result
- First waits 100ms using
For fn2
:
- Creates a new async function that:
- First waits 100ms using
await new Promise(resolve => setTimeout(resolve, 100))
- Then calls
fn2()
and returns its result
- First waits 100ms using
Step 2: Result array created
The result is [delayedFn1, delayedFn2]
where each is an async function.
Step 3: Using the delayed functions
When we call delayedFn1()
:
- Time 0ms: Function starts executing
- Time 0-100ms: Waiting during
await new Promise(resolve => setTimeout(resolve, 100))
- Time 100ms: Delay completes,
fn1()
is called - Time 100ms+:
fn1()
returns a promise resolving to5
- The async wrapper returns this promise, which resolves to
5
Visual Timeline:
Call delayedFn1() → Wait 100ms → Execute fn1() → Return promise(5) ↑ ↑ ↑ ↑ t=0ms t=100ms t=100ms+ Final result
Handling Rejections:
If fn1
instead returned a promise that rejects with error "failed"
:
- After the 100ms delay,
fn1()
would be called fn1()
returns a rejected promise- The async wrapper automatically propagates this rejection
delayedFn1()
returns a promise that rejects with"failed"
This demonstrates how the solution preserves both successful resolutions and rejections while adding the specified delay.
Solution Implementation
1import asyncio
2from typing import List, Callable, Any
3
4def delayAll(functions: List[Callable], ms: int) -> List[Callable]:
5 """
6 Delays the execution of an array of functions by a specified time
7
8 Args:
9 functions: Array of functions to be delayed
10 ms: Delay time in milliseconds
11
12 Returns:
13 Array of wrapped async functions that execute after the delay
14 """
15 # Map each function to a delayed async wrapper
16 delayed_functions = []
17
18 for fn in functions:
19 # Create an async wrapper function for each original function
20 async def create_delayed_function(original_fn=fn):
21 # Wait for the specified delay (convert ms to seconds for asyncio.sleep)
22 await asyncio.sleep(ms / 1000)
23 # Execute and return the result of the original function
24 return original_fn()
25
26 # Add the wrapped function to the result list
27 delayed_functions.append(create_delayed_function)
28
29 return delayed_functions
30
1import java.util.List;
2import java.util.ArrayList;
3import java.util.concurrent.CompletableFuture;
4import java.util.concurrent.TimeUnit;
5import java.util.function.Supplier;
6
7/**
8 * Utility class for delaying function execution
9 */
10public class FunctionDelayUtil {
11
12 /**
13 * Delays the execution of an array of functions by a specified time
14 * @param functions - List of functions to be delayed
15 * @param ms - Delay time in milliseconds
16 * @return List of wrapped suppliers that execute after the delay
17 */
18 public static List<Supplier<CompletableFuture<Object>>> delayAll(
19 List<Supplier<Object>> functions, long ms) {
20
21 // Create a new list to store the wrapped functions
22 List<Supplier<CompletableFuture<Object>>> delayedFunctions = new ArrayList<>();
23
24 // Iterate through each function in the input list
25 for (Supplier<Object> fn : functions) {
26 // Create a supplier that returns a CompletableFuture
27 Supplier<CompletableFuture<Object>> delayedSupplier = () -> {
28 // Create a CompletableFuture that completes after the specified delay
29 return CompletableFuture
30 .delayedExecutor(ms, TimeUnit.MILLISECONDS)
31 .execute(() -> {}) // Execute empty task after delay
32 .thenApply(v -> fn.get()); // Then execute the original function
33 };
34
35 // Alternative implementation using supplyAsync:
36 // Supplier<CompletableFuture<Object>> delayedSupplier = () ->
37 // CompletableFuture.supplyAsync(
38 // fn,
39 // CompletableFuture.delayedExecutor(ms, TimeUnit.MILLISECONDS)
40 // );
41
42 // Add the wrapped supplier to the result list
43 delayedFunctions.add(delayedSupplier);
44 }
45
46 // Return the list of delayed functions
47 return delayedFunctions;
48 }
49}
50
1#include <vector>
2#include <functional>
3#include <chrono>
4#include <thread>
5#include <future>
6
7/**
8 * Delays the execution of an array of functions by a specified time
9 * @param functions - Vector of functions to be delayed
10 * @param ms - Delay time in milliseconds
11 * @returns Vector of wrapped async functions that execute after the delay
12 */
13std::vector<std::function<std::future<void>()>> delayAll(
14 const std::vector<std::function<void()>>& functions,
15 int ms) {
16
17 // Create a vector to store the wrapped functions
18 std::vector<std::function<std::future<void>()>> delayedFunctions;
19
20 // Iterate through each function and create a delayed wrapper
21 for (const auto& fn : functions) {
22 // Create a wrapper function that returns a future
23 auto wrappedFunction = [fn, ms]() -> std::future<void> {
24 // Launch async task that delays then executes the function
25 return std::async(std::launch::async, [fn, ms]() {
26 // Wait for the specified delay in milliseconds
27 std::this_thread::sleep_for(std::chrono::milliseconds(ms));
28 // Execute the original function
29 fn();
30 });
31 };
32
33 // Add the wrapped function to the result vector
34 delayedFunctions.push_back(wrappedFunction);
35 }
36
37 return delayedFunctions;
38}
39
1/**
2 * Delays the execution of an array of functions by a specified time
3 * @param functions - Array of functions to be delayed
4 * @param ms - Delay time in milliseconds
5 * @returns Array of wrapped async functions that execute after the delay
6 */
7function delayAll(functions: Function[], ms: number): Function[] {
8 return functions.map((fn: Function) => {
9 // Return an async wrapper function for each original function
10 return async function (): Promise<any> {
11 // Wait for the specified delay
12 await new Promise<void>((resolve: () => void) => setTimeout(resolve, ms));
13 // Execute and return the result of the original function
14 return fn();
15 };
16 });
17}
18
Time and Space Complexity
Time Complexity: O(n)
where n
is the number of functions in the input array. The map
operation iterates through each function once to create a new delayed version, performing constant-time operations for each function (creating an async wrapper function).
Space Complexity: O(n)
where n
is the number of functions in the input array. The function creates a new array of the same size as the input array, with each element being a new async function wrapper. Additionally, each wrapper function creates a closure that captures the original function and the delay value ms
, but this doesn't change the overall linear space complexity.
Note on Execution Time: While the creation of the delayed functions has O(n)
time complexity, the actual execution of any returned function will have an additional O(1)
time complexity plus the delay of ms
milliseconds and the execution time of the original function itself.
Common Pitfalls
1. Late Binding Closure Issue in Python
The Python implementation has a critical bug due to late binding in closures. When creating functions inside a loop, the loop variable fn
is captured by reference, not by value. This means all delayed functions will reference the last function in the array.
Problem Example:
def delayAll(functions: List[Callable], ms: int) -> List[Callable]:
delayed_functions = []
for fn in functions:
async def create_delayed_function(): # Bug: fn is captured by reference
await asyncio.sleep(ms / 1000)
return fn() # All functions will call the last fn
delayed_functions.append(create_delayed_function)
return delayed_functions
Solution: Use default parameter binding or create a closure properly:
def delayAll(functions: List[Callable], ms: int) -> List[Callable]:
delayed_functions = []
for fn in functions:
async def create_delayed_function(original_fn=fn): # Fix: bind fn as default parameter
await asyncio.sleep(ms / 1000)
return original_fn()
delayed_functions.append(create_delayed_function)
return delayed_functions
2. Mixing Sync and Async Functions
The problem states functions return promises (async in Python), but the implementation doesn't handle the case where fn()
might be an async function itself.
Problem: If fn
is async, calling fn()
returns a coroutine that needs to be awaited:
async def create_delayed_function(original_fn=fn):
await asyncio.sleep(ms / 1000)
return original_fn() # If original_fn is async, this returns a coroutine, not the result
Solution: Check if the result is a coroutine and await it:
async def create_delayed_function(original_fn=fn):
await asyncio.sleep(ms / 1000)
result = original_fn()
if asyncio.iscoroutine(result):
return await result
return result
3. Incorrect Time Unit Conversion
JavaScript's setTimeout
uses milliseconds, while Python's asyncio.sleep
uses seconds. Forgetting to convert can lead to delays that are 1000x longer than intended.
Problem:
await asyncio.sleep(ms) # Bug: treats milliseconds as seconds
Solution:
await asyncio.sleep(ms / 1000) # Correct: converts ms to seconds
4. Not Preserving Function Arguments
The original problem doesn't specify if functions take arguments, but in real-world scenarios, the wrapped functions should accept and forward arguments.
Problem:
async def create_delayed_function(original_fn=fn):
await asyncio.sleep(ms / 1000)
return original_fn() # Cannot pass arguments to original function
Solution:
async def create_delayed_function(original_fn=fn):
async def wrapper(*args, **kwargs):
await asyncio.sleep(ms / 1000)
result = original_fn(*args, **kwargs)
if asyncio.iscoroutine(result):
return await result
return result
return wrapper
How many ways can you arrange the three letters A, B and C?
Recommended Readings
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Runtime Overview When learning about algorithms and data structures you'll frequently encounter the term time complexity This concept is fundamental in computer science and offers insights into how long an algorithm takes to complete given a certain input size What is Time Complexity Time complexity represents the amount of time
Want a Structured Path to Master System Design Too? Don’t Miss This!