TypeDrop
2026-03-02 Challenge
2026-03-02
Easy
Typed Contact Book with Safe Parsing & Lookup
You're building a lightweight contact management module for a small business app. Raw contact data arrives as unknown JSON from an import file, and you must validate it into strongly-typed records, build an efficient lookup index, and expose typed query helpers — all without reaching for `any`.
Goals
- Define a generic `Result<T, E>` discriminated union and a `ParseError` discriminated union with at least three variants.
- Implement `parseContact` to safely narrow `unknown` input into a typed `Contact`, returning descriptive `ParseError` values for every invalid case.
- Implement `buildIndex` using the `Record` utility type and `findById` returning `Contact | undefined` without any type assertions.
- Implement `filterByTags` and `searchByName` using typed iteration over the `ContactIndex`.
challenge.ts
export type PhoneType = "mobile" | "home" | "work";
export interface PhoneNumber {
type: PhoneType;
number: string;
}
export interface Contact {
id: string;
firstName: string;
lastName: string;
email: string;
phones: PhoneNumber[]; // at least one entry
tags: string[];
}
export type Result<T, E> = never; // TODO: define discriminated union
export type ParseError = never; // TODO: define discriminated union
export type ContactIndex = never; // TODO: use Record utility type
export function parseContact(raw: unknown): Result<Contact, ParseError> { ... }
export function buildIndex(contacts: Contact[]): ContactIndex { ... }
export function findById(index: ContactIndex, id: string): Contact | undefined { ... }
export function filterByTags(index: ContactIndex, requiredTags: string[]): Contact[] { ... }
export function searchByName(index: ContactIndex, prefix: string): Contact[] { ... }
Hints (click to reveal)
Hints
- A discriminated union needs a shared literal-typed field (e.g. `ok` or `kind`) so TypeScript can narrow between variants in `if`/`switch` blocks.
- To validate `phones` entries, check `Array.isArray` first, then iterate and verify each entry's `type` is one of the three `PhoneType` literals — a `const VALID_PHONE_TYPES` tuple with `includes` (after a cast-free helper) works well here.
- `Object.values(index)` gives you a `Contact[]` you can then `filter` and `sort` without any extra type work.
Useful resources
Or clone locally
git clone -b challenge/2026-03-02 https://github.com/niltonheck/typedrop.git