MediumJavaScriptTypeScript

Branded Types — Parse-Don't-Validate

TypeScriptTypesPatterns

Branded types (also called nominal types) allow you to distinguish values that have the same runtime type but different semantic meaning:

type UserId    = string & { readonly _brand: 'UserId' };
type ChallengeId = string & { readonly _brand: 'ChallengeId' };

function getChallenge(id: ChallengeId): Challenge { ... }

const userId: UserId = makeUserId('u_123');
getChallenge(userId); // ❌ TypeScript error — UserId ≠ ChallengeId

The _brand field is a phantom type — it only exists at compile time. At runtime, the value is just a string.

Parse-Don't-Validate

Instead of isSlug(x): boolean, return the branded type or throw:

// ❌ validate (you lose the type information):
if (isSlug(value)) { use(value); } // value is still string

// ✅ parse (the return type carries the proof):
const slug = parseSlug(value); // Slug | throws
use(slug); // slug is Slug ✅

Implement

type Slug     = string & { readonly _brand: 'Slug' };
type HexColor = string & { readonly _brand: 'HexColor' };

// Slug: lowercase letters, digits, hyphens — at least 2 chars
// Valid: 'hello-world', 'ts-generics-easy', 'my-challenge-1'
// Invalid: 'Hello', '-start', 'end-', 'a' (too short)

// HexColor: #RGB or #RRGGBB
// Valid: '#FFF', '#ff0000', '#A3B'
// Invalid: 'red', '#GGG', '#1234567' (7 digits)

Sample tests

Test #1valid slug passes
Input: ["slug","hello-world"]
Output: {"ok":true,"value":"hello-world"}
Test #2slug with prefix is valid
Input: ["slug","ts-easy"]
Output: {"ok":true,"value":"ts-easy"}
Test #3uppercase slug is invalid
Input: ["slug","Hello-World"]
Output: {"ok":false,"error":"Invalid slug: \"Hello-World\""}
Test #4slug cannot start with hyphen
Input: ["slug","-starts-with-dash"]
Output: {"ok":false,"error":"Invalid slug: \"-starts-with-dash\""}
Test #5valid 6-digit hex color
Input: ["hex","#FF0000"]
Output: {"ok":true,"value":"#FF0000"}
Test #6valid 3-digit hex color
Input: ["hex","#FFF"]
Output: {"ok":true,"value":"#FFF"}
Test #7color name is invalid
Input: ["hex","red"]
Output: {"ok":false,"error":"Invalid hex color: \"red\""}
Test #8invalid hex digits
Input: ["hex","#GGG"]
Output: {"ok":false,"error":"Invalid hex color: \"#GGG\""}