ProgrammingTypeScript architect

How is pattern matching implemented in TypeScript through discriminated unions? How should types be structured correctly and what pitfalls are there?

Pass interviews with Hintsage AI assistant

Answer

In TypeScript, pattern matching is implemented through "discriminated unions". Each object in the union is assigned a mandatory discriminator field (usually a string, for example type), which TypeScript uses to distinguish the variants.

Example:

type Success = { type: 'success'; data: string }; type Failure = { type: 'failure'; error: string }; type Result = Success | Failure; function handleResult(result: Result) { switch (result.type) { case 'success': // result: Success console.log(result.data); break; case 'failure': // result: Failure console.error(result.error); break; } }

In the switch/case or if statement on the discriminator field, TypeScript will narrow the type down to the specific variant.

Main advantages:

  • Strict typing — you cannot access a non-existent field.
  • Exhaustiveness checking — if not all variants are handled, sometimes an error is triggered (can be explicitly enforced).

Trick question

If a new variant is added to the discriminated union, will TypeScript strictly require updating all switch-case statements to handle the new variant?

Answer: No, only if you explicitly add handling for the "impossible" variant. For example, by using the never function:

Example:

function assertNever(x: never): never { throw new Error('Unexpected variant: ' + x); } function handle(r: Result) { switch(r.type) { case 'success': /* ... */; break; case 'failure': /* ... */; break; default: return assertNever(r); // TS will throw an error if a new type appears } }

Examples of real errors due to ignorance of the subtleties of the topic.


Story

After expanding the "Result" type with a new variant ('pending'), in several places in the application, old switch-case statements did not handle this case. As a result, some interfaces stopped working. The error was noticed only in production a week after the release.


Story

Attempting to use a discriminated union without a unique discriminator (the type field duplicated in two types) led to "blurring" of types: TypeScript stopped narrowing the type precisely, allowing access to non-existent fields without a compilation error. Several critical bugs reached production.


Story

In the project, pattern matching was implemented using if-else statements across several fields instead of using a single explicit discriminator. This complicated the transition to exhaustiveness checking using the never function and made the code less readable — the switch-case statements worked incorrectly, and new variants "broke" existing logic.