TypeDrop

2026-08-25 Challenge

2026-08-25 Hard

Typed Concurrent Task Scheduler with Priority Queues & Result Monads

You're building the background job engine for a data-pipeline platform. Jobs arrive with different priorities, resource tags, and retry budgets — the scheduler must run them concurrently up to a configurable concurrency limit, respect priority ordering, surface per-job typed results, and propagate structured errors without ever widening to `unknown` unsafely.

Goals

  • Implement `makeJobId` and `makeTagName` so they validate input and return correctly branded types — without using `as` or type assertions.
  • Implement `runJobWithRetry` with a typed retry loop that emits structured `SchedulerError` discriminated union values and returns a fully-typed `JobOutcome<O>`.
  • Implement `runScheduler` with a concurrency-limited promise pool that sorts jobs by priority, validates them, collects all outcomes, and returns a `SchedulerReport<O>` with accurate `elapsedMs`.
  • Implement `summarizeByTag` that builds a `Map<TagName, { succeeded: number; failed: number }>` by correlating the original job list with the scheduler report outcomes.
challenge.ts
// Key types and main function signatures

type Brand<T, B extends string> = T & { readonly [__brand]: B };

export type JobId   = Brand<string, "JobId">;
export type TagName = Brand<string, "TagName">;
export type Priority = "critical" | "high" | "normal" | "low";

export type Result<T, E> = Ok<T> | Err<E>;
export type SchedulerError =
  | { kind: "job_failed";       jobId: JobId; attempt: number; cause: unknown }
  | { kind: "retries_exhausted"; jobId: JobId; totalAttempts: number; lastCause: unknown }
  | { kind: "invalid_job";      jobId: JobId; reason: string };

export interface Job<O> {
  readonly id: JobId;
  readonly priority: Priority;
  readonly tags: ReadonlyArray<TagName>;
  readonly maxRetries: number;
  readonly run: () => Promise<O>;
}

// Run one job, retrying up to job.maxRetries times
export async function runJobWithRetry<O>(job: Job<O>): Promise<JobOutcome<O>>;

// Run all jobs respecting concurrency limit and priority order
export async function runScheduler<O>(
  jobs: ReadonlyArray<Job<O>>,
  config: SchedulerConfig,
): Promise<SchedulerReport<O>>;

// Aggregate outcomes by tag across the report
export function summarizeByTag<O>(
  jobs: ReadonlyArray<Job<O>>,
  report: SchedulerReport<O>,
): Map<TagName, { succeeded: number; failed: number }>;
Hints (click to reveal)

Hints

  • For the concurrency pool in `runScheduler`, maintain a `Set` of in-flight promises and `await` the fastest one to finish whenever the set reaches `config.concurrency` — `Promise.race` is your friend.
  • To construct a branded type without `as`, use a function that returns the value cast through the `Brand` helper — the key insight is that the brand is enforced at the *construction site*, not at every call site.
  • When building `summarizeByTag`, build a `Map<JobId, JobOutcome<O>>` first so you can look up each job's outcome in O(1) while iterating over tags.

Or clone locally

git clone -b challenge/2026-08-25 https://github.com/niltonheck/typedrop.git