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.
Useful resources
Or clone locally
git clone -b challenge/2026-08-25 https://github.com/niltonheck/typedrop.git