TypeDrop

2026-09-22 Challenge

2026-09-22 Medium

Typed Job Queue with Priority Scheduling & Result Collection

You're building the task-scheduling core for a background job processor. Jobs arrive with different priorities and payload shapes, a concurrency-limited runner executes them, and every result — success or failure — is collected into a strongly-typed summary the compiler fully understands.

Goals

  • Define a three-variant discriminated union `Job` and the generic `JobSuccess`, `JobFailure`, and `JobResult` types with correct constraints.
  • Build a `HandlerRegistry` mapped type that maps each job `kind` to a handler typed for exactly that variant — no broader union.
  • Implement `runQueue` to sort by priority, enforce a concurrency limit, capture every result (success or failure), and return a fully-typed `QueueSummary`.
  • Implement `getSuccesses` so callers automatically receive the narrowed `JobSuccess<JobByKind<K>>[]` type without any explicit annotation at the call site.
challenge.ts
// Key types & main function signature

export type Job =
  | { kind: "email";     id: string; priority: number; to: string; subject: string; body: string }
  | { kind: "report";    id: string; priority: number; reportId: string; format: "pdf" | "csv" | "xlsx" }
  | { kind: "thumbnail"; id: string; priority: number; imageUrl: string; widths: number[] };

export type JobByKind<K extends Job["kind"]> = Extract<Job, { kind: K }>;

export type JobResult<J extends Job> =
  | { status: "success"; job: J; output: string; durationMs: number }
  | { status: "failure"; job: J; error: string; retryable: boolean };

export type HandlerRegistry = { [K in Job["kind"]]: (job: JobByKind<K>) => Promise<JobResult<JobByKind<K>>> };

export async function runQueue(
  jobs: Job[],
  registry: HandlerRegistry,
  concurrency: number,
): Promise<QueueSummary> { /* TODO */ }

export function getSuccesses<K extends Job["kind"]>(
  results: JobResult<Job>[],
  kind: K,
): JobSuccess<JobByKind<K>>[] { /* TODO */ }
Hints (click to reveal)

Hints

  • For `HandlerRegistry`, a mapped type `{ [K in Job["kind"]]: JobHandler<JobByKind<K>> }` gives each key its own narrowly-typed handler — no union bleed-through.
  • To enforce concurrency, keep a count of active slots and use a queue of pending promises; resolve the next job as each slot frees up.
  • In `getSuccesses`, filter first by `result.job.kind === kind`, then by `result.status === "success"` — two type guards in sequence let TypeScript narrow all the way to `JobSuccess<JobByKind<K>>`.

Or clone locally

git clone -b challenge/2026-09-22 https://github.com/niltonheck/typedrop.git