TypeDrop
2026-10-26 Challenge
2026-10-26
Hard
Typed Concurrent Task Scheduler with Retry & Cancellation
You're building the background job engine for a data-pipeline platform. Tasks arrive with priorities, dependencies, and retry policies — the scheduler must run them with a concurrency cap, propagate cancellation cleanly, and return a fully-typed result manifest that distinguishes successes, failures, and cancellations without a single `any` in sight.
Goals
- Implement branded-type constructors (`toTaskId`, `toMs`) using a generic identity trick — no `as` allowed.
- Implement `OutputTypeOf<T>` as a conditional type using `infer` to extract the output type from a `TaskDefinition<TOutput>`.
- Implement `topologicalSort` using Kahn's BFS algorithm, detecting cycles and unknown dependency references.
- Implement `schedule` to run tasks with a concurrency cap, per-task timeouts via `AbortController`, retry back-off, dependency-failure cancellation, `failFast` mode, and external signal propagation — returning a fully-typed `ScheduleManifest`.
challenge.ts
// Core domain types + main function signature
type TaskId = string & { readonly __brand: "TaskId" };
type Ms = number & { readonly __brand: "Ms" };
type RetryPolicy =
| { kind: "none" }
| { kind: "fixed"; attempts: number; delayMs: Ms }
| { kind: "exponential"; attempts: number; baseDelayMs: Ms; maxDelayMs: Ms };
interface TaskDefinition<TOutput> {
readonly id: TaskId;
readonly priority: "critical" | "high" | "normal" | "low";
readonly dependsOn: ReadonlyArray<TaskId>;
readonly retry: RetryPolicy;
readonly timeoutMs: Ms;
readonly work: (signal: AbortSignal) => Promise<TOutput>;
}
type TaskResult<TOutput> =
| { status: "success"; id: TaskId; output: TOutput; attempts: number; durationMs: number }
| { status: "failure"; id: TaskId; error: unknown; attempts: number; durationMs: number }
| { status: "cancelled"; id: TaskId; reason: "timeout" | "dependency-failed" | "aborted" };
type TaskRegistry = Record<string, TaskDefinition<unknown>>;
type ScheduleManifest<TRegistry extends TaskRegistry> = {
readonly [K in keyof TRegistry]: TaskResult<OutputTypeOf<TRegistry[K]>>;
};
// Entry point — implement this!
async function schedule<TRegistry extends TaskRegistry>(
registry: TRegistry,
config: SchedulerConfig,
signal?: AbortSignal
): Promise<ScheduleManifest<TRegistry>>
Hints (click to reveal)
Hints
- For branded constructors without `as`: a function whose return type is declared as the branded type will let the compiler accept a plain `string`/`number` return — use a runtime validation guard and let the declared return type do the branding.
- For `OutputTypeOf`, write: `type OutputTypeOf<T> = T extends TaskDefinition<infer O> ? O : never;`
- Model the scheduler's inner loop as a recursive "drain" function: after each task settles, check which newly-unblocked tasks can enter the priority queue, then fill concurrency slots — `Promise.race` over in-flight tasks is your friend for the concurrency cap.
Useful resources
Or clone locally
git clone -b challenge/2026-10-26 https://github.com/niltonheck/typedrop.git