TypeDrop

2026-06-02 Challenge

2026-06-02 Easy

Typed Contact Book Grouper

You're building the display layer for a mobile contact book app. Raw contact entries arrive as `unknown` from a device sync API; your engine must validate them, normalize their data, and group them into a strongly-typed alphabetical index — with zero `any`.

Goals

  • Implement `isPlainObject` as a type predicate that distinguishes plain objects from arrays, primitives, and null.
  • Implement `validateContact` to validate each field according to its rules and normalize `fullName` (trim) and `phone` (blank string → null).
  • Implement `resolveAlphaKey` to map a contact's first character to the correct `AlphaKey` letter or the `"#"` fallback.
  • Implement `groupContacts` to process all raw entries, collect typed validation errors, build the alphabetical `ContactIndex`, and sort each bucket case-insensitively by `fullName`.
challenge.ts
export type ContactCategory = "personal" | "work";

export interface Contact {
  id: string;
  fullName: string;   // trimmed, non-empty
  email: string;      // must contain "@"
  phone: string | null;
  category: ContactCategory;
}

export type AlphaKey = Uppercase<
  "a"|"b"|"c"|"d"|"e"|"f"|"g"|"h"|"i"|"j"|"k"|"l"|"m"|
  "n"|"o"|"p"|"q"|"r"|"s"|"t"|"u"|"v"|"w"|"x"|"y"|"z"
> | "#";

export type ContactIndex = Partial<Record<AlphaKey, Contact[]>>;

export type GroupResult = { index: ContactIndex; errors: ValidationError[] };

// Main entry point — validate, normalize, and group raw contacts
export function groupContacts(rawEntries: unknown[]): GroupResult { ... }
Hints (click to reveal)

Hints

  • A type predicate (`value is Record<string, unknown>`) lets the compiler narrow `unknown` inside every `if` branch that calls your guard — combine `typeof`, `!== null`, and `!Array.isArray` checks.
  • The `AlphaKey` union is exhaustive: use `String.prototype.toUpperCase()` and check whether the result is a single A–Z letter (e.g. `/^[A-Z]$/.test(...)`) before falling back to `"#"`.
  • `Partial<Record<AlphaKey, Contact[]>>` means each bucket may be `undefined` — initialise a bucket with an empty array the first time you encounter a new key, then push and sort.

Or clone locally

git clone -b challenge/2026-06-02 https://github.com/niltonheck/typedrop.git