'Parse, don't validate' in a language that fights you: TypeScript's structural typing problem

The idea is old and beloved: turn validation into a type the compiler remembers. The trouble, one blogger argues, is that TypeScript treats every same-shaped type as identical, so an 'Email' is just a string and the guarantee evaporates.

In brief

An essay walks through making 'parse, don't validate' work in TypeScript, where structural typing means a validated Email is indistinguishable from any string. The fix is a branded type: a phantom symbol that makes Email incompatible with string at compile time and vanishes at runtime.

The thread's senior voices split on whether that's the real idea or a fake-out: one argued branding just labels a string as 'validated,' while another countered that all type guarantees are ultimately validation the compiler agrees to trust.

The distinction the whole essay hangs on

Christian Ekrem's post revives Alexis King's principle and asks whether it survives contact with TypeScript. His framing of the difference is tight: "a validator says this thing is fine, please continue. A parser says give me a blob, and I'll either give you back a more precise type or tell you why I can't." A validator throws its work away — once email: string passes a check, the type system forgets, so the same defensive check gets repeated downstream, what King called shotgun parsing.

The obstacle is TypeScript's structural typing: two types with the same shape are the same type, so Email cannot be genuinely distinct from string. The language, Ekrem writes, "won't insist, and it won't even gently nudge" you toward parsing. His workaround is a branded type — a phantom unique symbol that makes Email incompatible with plain string at compile time while erasing to nothing at runtime, with the parser as the sole boundary allowed to mint one. He contrasts this with Elm, Haskell, and F#, which pull you toward parsing through opaque types and smart constructors, and lands on a mindset over a library: "make the type system carry the proof, not your memory."

Is a branded type really 'parsing,' or validation wearing a badge?

The sharpest exchange questioned whether branding is the real technique at all. One engineer argued it falls short of the original idea:

"This is just validation that is using the type system to indicate the validation has already occurred … instead of the structure of the type holding that info inherently (e.g. struct Email { name: String, host: String })."

jerf, in a long-running sub-thread, collapsed the distinction: "It's the same thing … Any non-trivial use of a type system will involve making guarantees the type system itself can not actually express." A NonEmpty list's first-and-last guarantee is also validation someone ran once, then encoded so callers never redo it; branding is a weaker point on the same continuum, not a different species.

The question every TS team actually hits: does this explode into 2^N types?

The practical objection recurred: if you parse instead of validate, do you need UserWithBirthday, VerifiedUser, UserWithBirthdayAndEmail, one type per combination of present fields? A commenter who works in DDD worried this "can't be right." The strongest answer drew the boundary. One engineer showed the explosion is imaginary: an independent optional field stays a single T | null; you only reach for distinct types when fields are correlated as a state, "and then it's a discriminated union on a status field, which is N states, not 2^N." Another named the CS answer — refinement types that attach predicates checked at compile time — then gave the pragmatic rule: "String -> User is worth it, User -> UserWithBirthday is not."

Where the room actually lands: Zod at the edge

For all the type-system gymnastics, the consensus tool was blunter. A commenter deep in functional TypeScript and several others converged on validating with a schema library at the program's boundary and keeping ordinary types inside: "Zod is more ergonomic, has easier apis and is perfect for most users." One reader put the cost-benefit plainly: branded types and newtypes are cool but high-maintenance, and "unless you're building a library that millions depend on, it's probably not worth it." The essay's principle survives the language; the phantom-symbol acrobatics that prove it mostly don't leave the boundary.

Get breakdowns like this every day

The top Hacker News stories, unpacked — by email, Telegram, or RSS.

Next story · 22 of 26 Read more
🚀Tech

He asked if you'd restarted your Mac this week. Cue an uptime culture war

The post was a small Saturday ritual: restart the Mac, force-quit the 'are you sure?' dialog, feel a little rebellious. The thread turned it into an argument about what a reboot even signals.

★ 115 · 💬 220 · taonaw.com · 5 min