9. Runtime Scheduling

Microtask vs. Macrotask Ordering

midconcurrencydata-structures

This is the rule behind a famously confusing piece of JavaScript behavior: Promise.resolve().then(cb) always runs before the next setTimeout(cb2, 0), no matter what the source order looks like. The reason: after EVERY macrotask (the initial script, a timer callback, an I/O callback), ALL currently-queued microtasks (Promise callbacks) run to completion -- including any NEW microtasks they themselves queue -- before the next macrotask is even considered.

Tasks are functions fn(loop, log) -- they append a label to log and may call loop.queue_microtask(...) / loop.queue_macrotask(...) to enqueue more work, mirroring how a real callback can itself schedule more callbacks.

Implement EventLoop.run(): run macrotasks one at a time (FIFO order), and after EACH macrotask, fully drain the microtask queue -- including microtasks newly queued while draining -- before running the next macrotask. Return log.

Example

loop.queue_macrotask(macro1)  # queues 2 microtasks when it runs
loop.queue_macrotask(macro2)
loop.run()
# -> ["macro1", "micro1", "micro2", "macro2"]
#     both microtasks drain BEFORE macro2, even though macro2 was already
#     queued when macro1 started running

Constraints

  • Microtask draining must be a loop that keeps going as long as ANY microtasks remain -- including ones queued by a microtask that just ran, not just the ones present when draining started.

Real-world grounding

This exact ordering rule (drain microtasks fully between every macrotask) is part of the HTML/ECMAScript event loop specification, which is why it's identical across every JS runtime -- Node, browsers, Deno all implement the same rule.

hints
event_loop_tasks.pysign in to run
Loading...
test result

This is a preview. Sign in to run this exercise against the real tests.