Name-Keyed Async Task Scheduler with Sequential Lanes, Cancel and Clear by Name

Read the full interview experience this question came from →

Quick Overview

Implement a JavaScript task scheduler for async functions where tasks sharing a name run strictly in order while tasks with different names run concurrently. Follow-ups add cancelling the running task for a name and clearing every running and queued task by name, testing promise handling, race conditions and API design.

Name-Keyed Async Task Scheduler with Sequential Lanes, Cancel and Clear by Name

Company: Airwallex

Role: Frontend Engineer

Category: Software Engineering Fundamentals

Difficulty: medium

Interview Round: Technical Screen

Create a task scheduler for asynchronous functions, built in the style of an event emitter. Each task is submitted under a name together with an async function. The scheduler must satisfy two rules: - Tasks with the same name execute one after another: a task starts only after the previous task with that name has finished, and tasks with the same name run in the order they were scheduled. - Tasks with different names execute concurrently and independently: a slow or failing task under one name never delays or affects tasks under another name. Implement it in JavaScript or TypeScript. One possible starting interface, which you may extend if you explain why: ```ts interface TaskScheduler { schedule(name: string, task: () => Promise<unknown>): void; on(event: string, listener: (payload: unknown) => void): void; } ``` The interview continued with two follow-ups, given below as Parts 2 and 3. ### Clarifying Questions - Should `schedule` also return a promise for the task's result, or are results reported only through events? Which lifecycle events should the emitter publish? - If a task rejects or throws, should the next task with the same name still run? - Is there any limit on how many names may run at the same time? - Can a task call back into the scheduler, for example to schedule another task under its own name? ### Part 1 — Same-name order, cross-name concurrency Implement `schedule` and the event interface so that both rules hold. Show exactly how the next task for a name gets started, including when the current task fails. ```hint State per name Think about what you need to remember for each name to know whether something is already running, and what event should trigger the next task for that name. ``` ```hint Failures must not stall the line Decide what happens to the waiting tasks of a name when the running one rejects, and make sure that code path still starts the next task. ``` #### What This Part Should Cover - Strict per-name ordering with no overlap, and full independence across names - Starting the next task after both success and failure, including a synchronous throw - Cleaning up per-name state once a name has nothing left to run - How results and errors reach the caller: a returned promise, emitted events, or both ### Part 2 — Cancel the running task by name Add `cancel(name)`: cancel the task currently running under `name` and move on to the next scheduled task with that name. ```hint You cannot stop a promise A running async function cannot be stopped from outside. Think about what the scheduler can do on its own side, and what it could offer the task so the task can stop itself. ``` ```hint The late finisher The cancelled task's promise may still settle after the next task has started. Make sure that late settlement cannot advance the queue a second time or report a result. ``` #### Clarifying Questions for this Part - Must cancelling stop the task's actual work, or only stop the scheduler from waiting for it? - What should the code that scheduled the cancelled task observe? #### What This Part Should Cover - The precise meaning of cancellation, and how a cooperative task learns about it - Protection against the cancelled task's late settlement - What the cancelled task's caller and the event listeners observe - Behavior when nothing is running under that name ### Part 3 — Clear all tasks by name Add `clear(name)`: clear all running and scheduled tasks with that name. ```hint Clear versus cancel Compare which parts of the per-name state each operation touches, and decide whether a task scheduled right after a clear should run. ``` ```hint Nobody left waiting Every discarded task had a caller waiting on it. Decide how each of them learns that its task will never run. ``` #### What This Part Should Cover - Discarding the waiting tasks and settling or reporting each of them - Cancelling the running task by reusing the Part 2 logic, in the right order - Tasks scheduled during or after the clear - Releasing the name's state afterwards ### What a Strong Answer Covers - Correctness under re-entrancy: listeners or tasks that call `schedule`, `cancel` or `clear` while the scheduler is updating its state - No unbounded growth of per-name state, and queue operations that stay cheap when many tasks are waiting - Deterministic tests for ordering, concurrency, failure and cancellation - A clean separation between the public API and internal state ### Follow-up Questions - How would you add a global limit on the number of tasks running at once across all names, while keeping per-name order? - How would you add a timeout per task, and how does it interact with `cancel`? - How would you write deterministic unit tests for these rules without relying on real timers? - What changes if failed tasks must be retried with backoff before the next task with the same name starts?

Overview: Implement a JavaScript task scheduler for async functions where tasks sharing a name run strictly in order while tasks with different names run concurrently. Follow-ups add cancelling the running task for a name and clearing every running and queued task by name, testing promise handling, race conditions and API design.

Read the full Airwallex Frontend Engineer interview experience this question came from

|Home/Software Engineering Fundamentals/Airwallex
Airwallex logo
Airwallex
Aug 31, 2026
mediumFrontend EngineerTechnical ScreenSoftware Engineering Fundamentals
0
0

Create a task scheduler for asynchronous functions, built in the style of an event emitter. Each task is submitted under a name together with an async function. The scheduler must satisfy two rules:

  • Tasks with the same name execute one after another: a task starts only after the previous task with that name has finished, and tasks with the same name run in the order they were scheduled.
  • Tasks with different names execute concurrently and independently: a slow or failing task under one name never delays or affects tasks under another name.

Implement it in JavaScript or TypeScript. One possible starting interface, which you may extend if you explain why:

interface TaskScheduler {
  schedule(name: string, task: () => Promise<unknown>): void;
  on(event: string, listener: (payload: unknown) => void): void;
}

The interview continued with two follow-ups, given below as Parts 2 and 3.

Clarifying Questions Guidance

  • Should schedule also return a promise for the task's result, or are results reported only through events? Which lifecycle events should the emitter publish?
  • If a task rejects or throws, should the next task with the same name still run?
  • Is there any limit on how many names may run at the same time?
  • Can a task call back into the scheduler, for example to schedule another task under its own name?

Part 1 — Same-name order, cross-name concurrency

Implement schedule and the event interface so that both rules hold. Show exactly how the next task for a name gets started, including when the current task fails.

What This Part Should Cover Guidance

  • Strict per-name ordering with no overlap, and full independence across names
  • Starting the next task after both success and failure, including a synchronous throw
  • Cleaning up per-name state once a name has nothing left to run
  • How results and errors reach the caller: a returned promise, emitted events, or both

Part 2 — Cancel the running task by name

Add cancel(name): cancel the task currently running under name and move on to the next scheduled task with that name.

Clarifying Questions for this Part Guidance

  • Must cancelling stop the task's actual work, or only stop the scheduler from waiting for it?
  • What should the code that scheduled the cancelled task observe?

What This Part Should Cover Guidance

  • The precise meaning of cancellation, and how a cooperative task learns about it
  • Protection against the cancelled task's late settlement
  • What the cancelled task's caller and the event listeners observe
  • Behavior when nothing is running under that name

Part 3 — Clear all tasks by name

Add clear(name): clear all running and scheduled tasks with that name.

What This Part Should Cover Guidance

  • Discarding the waiting tasks and settling or reporting each of them
  • Cancelling the running task by reusing the Part 2 logic, in the right order
  • Tasks scheduled during or after the clear
  • Releasing the name's state afterwards

What a Strong Answer Covers Guidance

  • Correctness under re-entrancy: listeners or tasks that call schedule , cancel or clear while the scheduler is updating its state
  • No unbounded growth of per-name state, and queue operations that stay cheap when many tasks are waiting
  • Deterministic tests for ordering, concurrency, failure and cancellation
  • A clean separation between the public API and internal state

Follow-up Questions Guidance

  • How would you add a global limit on the number of tasks running at once across all names, while keeping per-name order?
  • How would you add a timeout per task, and how does it interact with cancel ?
  • How would you write deterministic unit tests for these rules without relying on real timers?
  • What changes if failed tasks must be retried with backoff before the next task with the same name starts?
Loading comments...