Suggest an editImprove this articleRefine the answer for “What is union in TypeScript”. Your changes go to moderation before they’re published.Approval requiredContentWhat you’re changing🇺🇸EN🇺🇦UAPreviewTitle (EN)Short answer (EN)**Union type** in TypeScript lets a variable hold one of several specified types, declared with `|`. TypeScript enforces narrowing before you access type-specific methods. ```typescript type Id = string | number; let userId: Id = "user-123"; // OK userId = 456; // OK ``` **Key point:** use `typeof id === "string"` or a literal check to narrow the type first, then access members safely.Shown above the full answer for quick recall.Answer (EN)Image**Union type** in TypeScript lets a variable hold one of several specified types, declared with the `|` operator. TypeScript tracks which type is active at each point and requires you to narrow before accessing type-specific members. ## Theory ### TL;DR - Think multi-tool: the value is a knife *or* a screwdriver *or* pliers, one at a time, but you carry options. - Union gives a *choice* (`A | B`), while intersection gives a *combination* (`A & B`). - TypeScript narrows the union type automatically inside `typeof`, `instanceof`, or literal checks. - Use union for 2-3 known alternatives; switch to a discriminated union for complex branching. ### Quick example ```typescript type Id = string | number; let userId: Id = "user-123"; // OK: string userId = 456; // OK: number userId = true; // Error: boolean not in union // TypeScript narrows the type inside the branch if (typeof userId === "string") { console.log(userId.toUpperCase()); // string methods available here } ``` After the check, TypeScript knows `userId` is a `string` inside that block. Outside, it stays `string | number`. ### Key difference Union gives the compiler a *choice*: the value is exactly one of the listed types, not a combination of all of them. To use type-specific methods or properties, you narrow first with `typeof`, `instanceof`, or a literal check. Intersection (`&`) works the other way: the value must satisfy all types at once. ### When to use - API response field can be `string | null` → union. - Config option accepts `number | boolean` → union. - Event data varies by type → discriminated union with a shared `kind` field. - Four or more alternatives with complex narrowing → consider branded types. ### How TypeScript handles this At compile time, TypeScript tracks which types are still possible at each point in the code. This is called control flow analysis. When you write `if (typeof x === "string")`, the compiler narrows `x` to `string` inside that block and removes that branch from the remaining type. At runtime, none of this exists. Union types are fully erased after compilation, leaving plain JavaScript values. ### Common mistakes **Accessing methods without narrowing first:** ```typescript let id: string | number = "abc"; id.toFixed(); // Error: toFixed does not exist on string ``` Fix: `if (typeof id === "number") id.toFixed();` **Overly broad primitive union:** ```typescript function process(value: string | number | boolean) { value.length; // Error: length does not exist on number | boolean } ``` No shared methods across all three. Either narrow explicitly or restructure with a discriminated union. **Expecting intersection behavior from a union:** ```typescript type A = { a: string }; type B = { b: number }; let x: A | B = { a: "hi" }; x.b; // Error: might be type A, which has no b ``` `A | B` means *one of*, not *both*. For overlap, use `A & B`. **Forgetting `null` from fetch responses:** ```typescript async function getName(): Promise<string> { // Wrong const res = await fetch("/name"); return res.json(); // Actually Promise<string | null> } ``` Fix: type it as `Promise<string | null>` and add a null check before returning. ### Real-world usage - React: `ReactNode = null | string | number | ReactElement` (the type of `children`). - Express: route params typed as `string | undefined`. - Node.js: `fs.readFile` callback receives `NodeJS.ErrnoException | null` as the first argument. - tRPC: discriminated unions for typed API error responses. ### Follow-up questions **Q:** What is a discriminated union? **A:** A union where each member has a shared literal field like `kind: "not-found"`. TypeScript narrows automatically in `if` or `switch` based on that field, no extra checks needed. **Q:** Union vs `any`: what is the difference? **A:** `any` disables type checking entirely. Union keeps it: you must narrow before accessing type-specific members, so mistakes show up at compile time. **Q:** Can unions nest? **A:** Yes. `(string | number[]) | boolean` is valid. The compiler flattens it internally for checking. **Q:** Any runtime performance cost? **A:** None. Union types are erased at compile time and exist only during static analysis. **Q:** How does control flow analysis work with the `in` operator in TypeScript 4.9+? **A:** `if ("prop" in obj)` narrows `obj` to the shapes that include that property. Given `{a: 1} | {b: 2}`, after `if ("a" in obj)` TypeScript knows `obj` has `a`. Useful when there is no shared discriminant field. ## Examples ### Optional image source in a React component A prop that accepts a loaded URL or `null` as a fallback. ```typescript interface ImageProps { src: string | null; // loaded image or placeholder alt: string; } function Image({ src, alt }: ImageProps) { return ( <img src={src || "placeholder.png"} // null handled safely alt={alt} /> ); } <Image src="photo.jpg" alt="Cat" />; // string <Image src={null} alt="No image yet" />; // null → placeholder ``` The union makes the optional state explicit. Without `null` in the type, TypeScript would reject the second call at compile time. ### Discriminated union for API error handling When an API returns different error shapes, a `kind` field gives TypeScript enough information to narrow automatically. ```typescript type ApiError = | { kind: "validation"; message: string; field: string } | { kind: "not-found"; id: number } | { kind: "server"; status: number }; function handleError(error: ApiError) { if (error.kind === "validation") { console.log(error.field); // OK, narrowed to validation shape } else if (error.kind === "not-found") { console.log(error.id); // OK, narrowed to not-found shape } // error.message outside a branch → Error: not all members have it } ``` I have seen this break when a developer adds a new union member but forgets to handle it in the chain. A final `else` that throws makes the gap visible at runtime. For exhaustive compile-time checking, a `switch` with a `never` assertion in the `default` case is even cleaner.For the reviewerNote to the moderator (optional)Visible only to the moderator. Helps review go faster.