0
0
Typescriptprogramming~15 mins

Union type syntax and behavior in Typescript - Deep Dive

Choose your learning style9 modes available
Overview - Union type syntax and behavior
What is it?
Union types in TypeScript allow a variable to hold more than one type of value. You write them by joining types with a vertical bar (|), like string | number. This means the variable can be either a string or a number, giving flexibility while still checking types. It helps catch errors before running the code.
Why it matters
Without union types, you would have to choose only one type for a variable or use very loose types that don't check well. This can cause bugs when unexpected types appear. Union types let you write safer code that still handles different kinds of data, making programs more reliable and easier to maintain.
Where it fits
Before learning union types, you should understand basic TypeScript types like string, number, and boolean. After union types, you can learn about intersection types, type guards, and advanced type narrowing to handle complex data safely.
Mental Model
Core Idea
A union type is like a container that can hold one of several specified types, allowing flexible but safe values.
Think of it like...
Imagine a mailbox that can accept letters or small packages. The mailbox is designed to hold either type, but nothing else. Union types work the same way for variables—they accept one of several allowed types.
Variable: value
  ├─ Type A (e.g., string)
  └─ Type B (e.g., number)

TypeScript checks that the value fits one of these types before allowing it.
Build-Up - 7 Steps
1
FoundationBasic type declarations
🤔
Concept: Learn how to declare variables with single types.
In TypeScript, you declare a variable with a type like this: let age: number = 30; let name: string = 'Alice'; This means age can only hold numbers, and name can only hold strings.
Result
Variables hold only the declared type values, preventing mistakes like assigning a string to a number variable.
Understanding single-type declarations is essential before combining types with unions.
2
FoundationWhat is a union type?
🤔
Concept: Introduce the syntax and meaning of union types.
A union type lets a variable hold values of multiple types. You write it using the | symbol: let value: string | number; Now value can be a string or a number.
Result
The variable can safely hold either type, and TypeScript will check assignments accordingly.
Union types add flexibility while keeping type safety, unlike using 'any' which disables checks.
3
IntermediateUsing union types in functions
🤔Before reading on: do you think a function with a union type parameter can use all methods of both types directly? Commit to your answer.
Concept: How union types affect function parameters and method usage.
Consider a function: function printId(id: string | number) { console.log('ID:', id); } You can pass either a string or number. But inside the function, you cannot use methods that only exist on one type without checking first.
Result
You get errors if you try to call string-only or number-only methods without checking the type first.
Knowing that union types restrict direct use of type-specific methods encourages safe type checks.
4
IntermediateType narrowing with union types
🤔Before reading on: do you think TypeScript can automatically know the exact type inside an if statement? Commit to your answer.
Concept: Learn how to safely use union types by checking the actual type at runtime.
You can check the type using typeof: function printId(id: string | number) { if (typeof id === 'string') { console.log(id.toUpperCase()); // safe } else { console.log(id.toFixed(2)); // safe } } This is called type narrowing.
Result
TypeScript understands the type inside each branch and allows type-specific methods.
Type narrowing is key to safely working with union types and avoiding runtime errors.
5
IntermediateUnion types with arrays and objects
🤔
Concept: How union types work with complex data like arrays and objects.
You can have union types for arrays: let items: (string | number)[] = ['apple', 10, 'banana']; Or for object properties: interface User { id: string | number; } This means each item or property can be one of the union types.
Result
You get flexible collections or objects that accept multiple types safely.
Union types extend beyond simple variables to complex data structures, increasing flexibility.
6
AdvancedDiscriminated unions for safe type checks
🤔Before reading on: do you think union types can be combined with a special property to identify each type? Commit to your answer.
Concept: Learn about discriminated unions that use a common property to distinguish types safely.
Discriminated unions add a shared property with different literal values: interface Square { kind: 'square'; size: number; } interface Circle { kind: 'circle'; radius: number; } type Shape = Square | Circle; function area(shape: Shape) { if (shape.kind === 'square') { return shape.size * shape.size; } else { return Math.PI * shape.radius ** 2; } } This pattern helps TypeScript know exactly which type it is.
Result
You can safely access properties without extra checks, reducing errors.
Discriminated unions are a powerful pattern for working with complex union types in real code.
7
ExpertUnion type behavior with type inference and control flow
🤔Before reading on: do you think TypeScript always narrows union types perfectly in all control flow cases? Commit to your answer.
Concept: Explore how TypeScript infers and narrows union types during complex control flows and where it can fail.
TypeScript uses control flow analysis to narrow union types, but some patterns confuse it: function example(x: string | number | boolean) { if (typeof x === 'string') { // x is string here } else if (typeof x === 'number') { // x is number here } else { // x is boolean here } } But in some cases, like nested functions or callbacks, TypeScript may lose track and keep the union type, requiring explicit assertions or checks.
Result
Understanding these limits helps write clearer, safer code and avoid subtle bugs.
Knowing TypeScript's control flow narrowing limits prevents confusing errors and improves debugging.
Under the Hood
TypeScript's compiler analyzes the code and tracks the types of variables. When it sees a union type, it allows any value that matches any of the types in the union. During type checking, it uses control flow analysis to narrow down the possible types based on conditions like typeof checks. This narrowing lets it allow only the methods and properties valid for the narrowed type. At runtime, union types do not exist; they are erased, so JavaScript runs without type checks.
Why designed this way?
Union types were introduced to balance flexibility and safety. Before unions, developers had to use 'any' or multiple variables, which led to bugs. The design uses compile-time checks to catch errors early without runtime cost. The vertical bar syntax is simple and visually clear, inspired by set theory and other typed languages. Alternatives like intersection types serve different purposes, so unions focus on 'either-or' scenarios.
┌─────────────┐
│ Variable x  │
│ Type: A | B │
└─────┬───────┘
      │
      ├─ If x is A ──> Use A's methods
      │
      └─ If x is B ──> Use B's methods

TypeScript compiler
  └─ Checks assignments
  └─ Narrows types with conditions

Runtime JavaScript
  └─ No types, just values
Myth Busters - 4 Common Misconceptions
Quick: Does a union type variable always have all methods of all types? Commit to yes or no.
Common Belief:A variable with a union type can use all methods from all types in the union without checks.
Tap to reveal reality
Reality:You can only use methods common to all types or after narrowing the type with checks.
Why it matters:Trying to use type-specific methods without checks causes compile errors and confusion.
Quick: Does union type checking happen at runtime? Commit to yes or no.
Common Belief:Union types enforce type checks when the program runs.
Tap to reveal reality
Reality:TypeScript checks union types only at compile time; at runtime, types are erased.
Why it matters:Relying on runtime type safety from union types leads to bugs because JavaScript does not enforce types.
Quick: Can union types include 'null' or 'undefined' implicitly? Commit to yes or no.
Common Belief:Union types automatically include null or undefined if one type is nullable.
Tap to reveal reality
Reality:You must explicitly include null or undefined in the union to allow those values.
Why it matters:Assuming null or undefined is included can cause unexpected runtime errors.
Quick: Does TypeScript always perfectly narrow union types in all code paths? Commit to yes or no.
Common Belief:TypeScript always narrows union types correctly in every control flow scenario.
Tap to reveal reality
Reality:TypeScript sometimes cannot narrow types in complex or nested control flows, requiring manual checks.
Why it matters:Overestimating narrowing can cause type errors or unsafe code if assumptions are wrong.
Expert Zone
1
Union types combined with literal types enable powerful discriminated unions for exhaustive type checking.
2
TypeScript's control flow analysis for narrowing union types can be bypassed by closures or async code, requiring explicit type assertions.
3
Union types interact subtly with generics and conditional types, allowing advanced type transformations but increasing complexity.
When NOT to use
Avoid union types when you need a value to have all properties of multiple types simultaneously; use intersection types instead. Also, if runtime type safety is critical, consider runtime validation libraries because TypeScript types are erased at runtime.
Production Patterns
In real-world code, union types are used for flexible API inputs, event handling where multiple event types are possible, and modeling data that can come in different shapes. Discriminated unions are common in Redux reducers and state machines for safe state management.
Connections
Intersection types
Opposite pattern that requires a value to satisfy all types simultaneously, unlike union types which allow any one of several types.
Understanding union types clarifies when to use intersection types, as they solve complementary problems in type composition.
Set theory
Union types correspond to the union operation in set theory, combining multiple sets of possible values.
Knowing set theory helps grasp why union types allow any value from multiple sets, grounding type theory in math.
Natural language ambiguity
Union types resemble words with multiple meanings, where context narrows the meaning, similar to type narrowing in code.
This connection shows how humans resolve ambiguity by context, just like TypeScript narrows union types during control flow.
Common Pitfalls
#1Trying to call a method that exists only on one type in a union without checking the type first.
Wrong approach:function printLength(x: string | number) { console.log(x.length); // Error: length does not exist on number }
Correct approach:function printLength(x: string | number) { if (typeof x === 'string') { console.log(x.length); // Safe } else { console.log('No length for number'); } }
Root cause:Misunderstanding that union types restrict method access to only those common to all types unless narrowed.
#2Assuming union types check types at runtime and prevent invalid values.
Wrong approach:let value: string | number = 'hello'; value = true; // No runtime error, but should be invalid
Correct approach:let value: string | number = 'hello'; // value = true; // Compile-time error prevents this assignment
Root cause:Confusing TypeScript's compile-time checks with runtime behavior, forgetting types are erased.
#3Not including null or undefined explicitly in union types when needed.
Wrong approach:let name: string | number; name = null; // Error: null not assignable
Correct approach:let name: string | number | null; name = null; // Allowed
Root cause:Assuming nullable values are included by default in union types.
Key Takeaways
Union types let variables hold values of multiple specified types, increasing flexibility while keeping type safety.
You must use type narrowing techniques like typeof checks to safely access type-specific methods on union types.
TypeScript performs all union type checks at compile time; no runtime type enforcement exists.
Discriminated unions use a shared property to distinguish types, enabling safe and clear code for complex unions.
Understanding the limits of TypeScript's type narrowing helps avoid subtle bugs in complex control flows.