1. Problems 91–100
Same format as the previous modules: expand a problem to see the approach and both
commented solutions. Several problems here reuse a shared helper —
a task(id, duration) function that waits, then returns a label — to keep
the timing comparisons easy to follow.
P91
Sleep / Delay
Wait 2 seconds, then continue.
Sleep / Delay
Wait 2 seconds, then continue.
Approach: JavaScript has no built-in sleep — wrap setTimeout in a Promise that resolves once the timer fires, then await it. Python's asyncio ships sleep() directly.
JavaScript
// A Promise that resolves after a timeout is the standard way to "await" time
// passing in JavaScript -- there's no built-in sleep() function.
function sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
async function demo() {
console.log("Waiting...");
await sleep(2000);
console.log("Executed after 2 seconds");
}
demo();
Python
import asyncio
async def demo():
print("Waiting...")
await asyncio.sleep(2) # Python's asyncio ships sleep() directly -- no wrapper needed
print("Executed after 2 seconds")
asyncio.run(demo())
P92
Create and Resolve a Promise Manually
Reject on an invalid ID, resolve with a name otherwise.
Create and Resolve a Promise Manually
Reject on an invalid ID, resolve with a name otherwise.
Approach: a new Promise(executor) runs its executor immediately; calling resolve() or reject() settles it. A Python coroutine is the closest equivalent — defining it doesn't run it, awaiting it does, and raising an exception is how it "rejects."
JavaScript
// The executor function runs immediately; calling resolve() or reject() settles
// the Promise, which .then()/await can react to afterward.
function fetchUserName(userId) {
return new Promise((resolve, reject) => {
if (userId <= 0) {
reject(new Error("Invalid user ID"));
} else {
resolve(`User${userId}`);
}
});
}
async function demo() {
const name = await fetchUserName(42);
console.log(name); // "User42"
}
demo();
Python
import asyncio
async def fetch_user_name(user_id: int) -> str:
"""A coroutine is Python's Promise equivalent -- calling it doesn't run it yet;
awaiting it does, and raising an exception is how it "rejects"."""
if user_id <= 0:
raise ValueError("Invalid user ID")
return f"User{user_id}"
async def demo():
name = await fetch_user_name(42)
print(name) # "User42"
asyncio.run(demo())
P93
Run Async Tasks Sequentially
Three 300ms tasks, one after another, take ~900ms total.
Run Async Tasks Sequentially
Three 300ms tasks, one after another, take ~900ms total.
Approach: awaiting each call before starting the next means the second task doesn't even begin until the first has fully finished — their durations simply add up.
JavaScript
// Awaiting each call before starting the next means task 2 doesn't begin until
// task 1 has fully finished -- their delays add up.
async function task(id, ms) {
await sleep(ms);
return `Task ${id} done`;
}
async function runSequentially() {
const results = [];
results.push(await task(1, 300));
results.push(await task(2, 300));
results.push(await task(3, 300));
return results; // takes roughly 900ms total
}
Python
import asyncio
async def task(task_id: int, seconds: float) -> str:
await asyncio.sleep(seconds)
return f"Task {task_id} done"
async def run_sequentially() -> list:
results = []
results.append(await task(1, 0.3))
results.append(await task(2, 0.3))
results.append(await task(3, 0.3))
return results # takes roughly 0.9s total
P94
Run Async Tasks in Parallel
The same three tasks, started together, take ~300ms total.
Run Async Tasks in Parallel
The same three tasks, started together, take ~300ms total.
Approach: Promise.all/asyncio.gather start every task immediately instead of one at a time, then wait for all of them together — the total time becomes roughly the slowest single task, not the sum of all of them.
JavaScript
// Promise.all() starts every task immediately and waits for all of them --
// the total time is roughly the SLOWEST task, not the sum of all of them.
async function runInParallel() {
const results = await Promise.all([
task(1, 300),
task(2, 300),
task(3, 300),
]);
return results; // takes roughly 300ms total, not 900ms
}
Python
import asyncio
async def run_in_parallel() -> list:
"""asyncio.gather() is Python's Promise.all() -- run everything concurrently,
wait for all of it, and get results back in the original order."""
results = await asyncio.gather(
task(1, 0.3),
task(2, 0.3),
task(3, 0.3),
)
return results # takes roughly 0.3s total, not 0.9s
P95
Race Multiple Tasks, Take Whichever Finishes First
A 500ms task and a 100ms task race — the fast one wins.
Race Multiple Tasks, Take Whichever Finishes First
A 500ms task and a 100ms task race — the fast one wins.
Approach: Promise.race settles the instant the first promise settles, win or lose — the rest keep running in the background but their results are ignored. asyncio needs one extra step: cancelling the losing tasks yourself.
JavaScript
// Promise.race() settles as soon as the FIRST promise settles -- the others
// keep running in the background but their results are ignored.
async function raceExample() {
const winner = await Promise.race([
task("slow", 500),
task("fast", 100),
]);
return winner; // "Task fast done"
}
Python
import asyncio
async def race_example() -> str:
"""asyncio.wait(..., return_when=FIRST_COMPLETED) is the closest match to
Promise.race() -- it also leaves you to cancel the losers yourself."""
tasks = [asyncio.create_task(task("slow", 0.5)), asyncio.create_task(task("fast", 0.1))]
done, pending = await asyncio.wait(tasks, return_when=asyncio.FIRST_COMPLETED)
for p in pending:
p.cancel() # Promise.race() doesn't cancel losers automatically -- asyncio does need this
return done.pop().result() # "Task fast done"
P96
Handle Multiple Failures Without Stopping Early
One task fails, two succeed — report all three outcomes.
Handle Multiple Failures Without Stopping Early
One task fails, two succeed — report all three outcomes.
Approach: Promise.all/plain gather() abort the moment any one task fails. Promise.allSettled waits for every task regardless and reports each one's individual outcome — Python's version is gather(return_exceptions=True), which returns failures as exception objects instead of raising.
JavaScript
// Promise.all() rejects immediately if ANY promise rejects. Promise.allSettled()
// instead waits for every promise and reports each one's outcome individually.
async function settleAll() {
const results = await Promise.allSettled([
Promise.resolve("ok"),
Promise.reject(new Error("failed")),
Promise.resolve("ok too"),
]);
return results.map((r) => r.status); // ["fulfilled", "rejected", "fulfilled"]
}
Python
import asyncio
async def _ok():
return "ok"
async def _fails():
raise ValueError("failed")
async def settle_all() -> list:
"""return_exceptions=True turns gather() into allSettled(): failures come back
as exception objects in the results list instead of raising immediately."""
results = await asyncio.gather(_ok(), _fails(), _ok(), return_exceptions=True)
return ["rejected" if isinstance(r, Exception) else "fulfilled" for r in results]
# ["fulfilled", "rejected", "fulfilled"]
P97
Add a Timeout to an Async Operation
Give up on a 2-second task if it hasn't finished in 500ms.
Add a Timeout to an Async Operation
Give up on a 2-second task if it hasn't finished in 500ms.
Approach: JavaScript builds a timeout by racing the real operation against a Promise that rejects after a deadline — whichever settles first wins. Python's asyncio.wait_for() bakes that exact pattern in as a single call.
JavaScript
// Race the real operation against a Promise that rejects after a deadline --
// whichever settles first wins.
function withTimeout(promise, ms) {
const timeout = new Promise((_, reject) =>
setTimeout(() => reject(new Error("Timed out")), ms)
);
return Promise.race([promise, timeout]);
}
async function demo() {
try {
const result = await withTimeout(task("slow", 2000), 500);
console.log(result);
} catch (err) {
console.log(err.message); // "Timed out"
}
}
Python
import asyncio
async def demo():
try:
# asyncio.wait_for() bakes the race-against-a-deadline pattern in directly.
result = await asyncio.wait_for(task("slow", 2), timeout=0.5)
print(result)
except asyncio.TimeoutError:
print("Timed out")
asyncio.run(demo())
P98
Retry a Failing Async Operation
Module 9's retry(), rewritten for an async task.
Retry a Failing Async Operation
Module 9's retry(), rewritten for an async task.
Approach: identical structure to Module 9's synchronous retry(), but each attempt is awaited, since the operation being retried is itself asynchronous.
JavaScript
// Same idea as the synchronous retry() from Module 9, but every attempt is
// awaited since the operation itself is asynchronous.
async function retryAsync(fn, maxAttempts) {
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
try {
return await fn();
} catch (err) {
if (attempt === maxAttempts) throw err;
console.log(`Attempt ${attempt} failed, retrying...`);
}
}
}
Python
async def retry_async(fn, max_attempts: int):
for attempt in range(1, max_attempts + 1):
try:
return await fn()
except Exception:
if attempt == max_attempts:
raise
print(f"Attempt {attempt} failed, retrying...")
P99
Limit Concurrency (Promise Pool)
Run 100 tasks, but never more than 5 at the same time.
Limit Concurrency (Promise Pool)
Run 100 tasks, but never more than 5 at the same time.
Approach: instead of starting every task at once, cap how many run concurrently — each finished task frees a slot for the next one to start. Python's asyncio.Semaphore implements exactly this limiter as a reusable primitive.
JavaScript
// Instead of starting every task at once, cap how many run at the same time --
// each finished task pulls the next one off the queue.
async function promisePool(tasks, limit) {
const results = [];
const executing = new Set();
for (const [index, taskFn] of tasks.entries()) {
const p = taskFn().then((result) => { results[index] = result; executing.delete(p); });
executing.add(p);
if (executing.size >= limit) {
await Promise.race(executing); // wait for a slot to free up before adding more
}
}
await Promise.all(executing);
return results;
}
Python
import asyncio
async def promise_pool(task_fns: list, limit: int) -> list:
"""A Semaphore is Python's built-in concurrency limiter: acquire() blocks once
`limit` tasks are already running, and release() lets the next one through."""
semaphore = asyncio.Semaphore(limit)
async def run_with_limit(task_fn):
async with semaphore:
return await task_fn()
return await asyncio.gather(*(run_with_limit(fn) for fn in task_fns))
P100
Async Queue — Process Items One at a Time
A worker loop that handles one queued item after another.
Async Queue — Process Items One at a Time
A worker loop that handles one queued item after another.
Approach: a simple worker loop — pull the next item off the front of the queue and await its handler before moving on to the one after it. This is concurrency limit 1 as a special case of problem 99.
JavaScript
// A simple worker loop: pull items off the front of the queue and await each
// one's processing before moving to the next.
async function processQueue(items, handler) {
const results = [];
const queue = [...items];
while (queue.length > 0) {
const item = queue.shift(); // take the next item off the front
results.push(await handler(item));
}
return results;
}
Python
import asyncio
async def process_queue(items: list, handler) -> list:
"""collections.deque would be more efficient than a list for repeated pop(0),
but the shape of the algorithm is identical either way."""
results = []
queue = list(items)
while queue:
item = queue.pop(0) # take the next item off the front
results.append(await handler(item))
return results
2. Key Takeaways
- Sequential vs. parallel is the single biggest lever on total runtime:
await-ing tasks one at a time adds their durations;Promise.all/asyncio.gatherruns them concurrently and takes roughly the slowest one. - Every
Promisestatic method has a directasynciocounterpart:all→gather,race→wait(FIRST_COMPLETED),allSettled→gather(return_exceptions=True)— the concepts transfer even though the APIs look different. - A concurrency limit (Semaphore/pool) exists to protect a downstream resource — a rate-limited API, a database connection pool — from being hit by every task at once, even when nothing stops you from launching them all simultaneously.