ProgrammingFrontend Developer

How is the union types mechanism structured in TypeScript? What is it for, how does type narrowing work, and what are the nuances of working with union types?

Pass interviews with Hintsage AI assistant

Answer.

Background:

Union types emerged in TypeScript to describe variables and parameters that can have values of different types. This capability significantly expanded the flexibility of typing compared to classical languages.

Problem:

In JavaScript, functions and variables are often multi-format (for example, accepting either a number or a string), which complicates safe typing. Without union types, it was necessary to use any or duplicate code. This increased the number of errors in production and made teamwork more difficult in large teams.

Solution:

Union types allow declaring a variable that can be one of several types, ensuring correct operations after checks. Type narrowing support is also implemented, helping the compiler "understand" what it is dealing with.

Example code:

function formatId(id: number | string): string { if (typeof id === 'string') { return id.toUpperCase(); } return id.toString(); }

Key features:

  • Allow explicit specification of possible variable and parameter types.
  • Work together with the type narrowing mechanism through checks (e.g., typeof or in).
  • Complicate working with objects, where the same key can have different types—careful management of access logic is required.

Trick questions.

Can you write a union of objects with different properties and access any property without a check?

No. Union types allow you to access only those properties and methods that are present in all types. Accessing private properties requires type narrowing.

Example code:

type Fish = { swim: () => void; }; type Bird = { fly: () => void; }; function move(animal: Fish | Bird) { // animal.swim(); // Error without narrowing if ('swim' in animal) { animal.swim(); // OK } }

Why are not all methods available for the union type string | number?

TypeScript in union only allows what is present in all included types. Individual methods need to be checked for the actual type first.

What happens if you don’t check the type in a union and try to call a specific method?

In that case, a compilation error will occur, as the presence of the method is not guaranteed. It only works after checking the specific type.

Typical errors and anti-patterns

  • Skipping type checks before use (can cause access errors).
  • Describing overly broad union types (losing type safety).
  • Incorrect narrowing leads to hidden errors and not exhaustive checks.

Real-life example

Negative case

A variable was given the type string | number and toUpperCase() was called without a check. As a result, the application crashes on numeric data.

Pros:

  • Quickly written code.

Cons:

  • Runtime errors.
  • Loss of trust in TypeScript’s static typing.

Positive case

Check the type before working with the method:

if (typeof value === 'string') { return value.toUpperCase(); } else { return value.toString(); }

Pros:

  • Full safety at compile time.
  • Improved code maintainability.

Cons:

  • The necessity to write extra checks.