Discriminated union

2 snippets in TypeScript

TSTypeScript

Union Types

TS · Type Aliases
syntax
type Name = TypeA | TypeB | TypeC;
example
type Status = "pending" | "active" | "archived";
type ApiResponse = SuccessResult | ErrorResult;

interface SuccessResult {
  ok: true;
  data: unknown;
}
interface ErrorResult {
  ok: false;
  errorCode: number;
  message: string;
}

function handleResponse(res: ApiResponse) {
  if (res.ok) {
    console.log(res.data); // narrowed to SuccessResult
  } else {
    console.error(res.message); // narrowed to ErrorResult
  }
}
output
// Union means 'one of these types' — must narrow before accessing specific members

Note You can only access properties common to ALL members of a union without narrowing first. Use a shared discriminant property (like 'ok' above) to let TypeScript narrow automatically in conditionals.

Discriminated Unions

TS · Type Guards & Narrowing
syntax
interface A { kind: "a"; ... }
interface B { kind: "b"; ... }
type Union = A | B;
example
interface LoadingState {
  status: "loading";
}

interface SuccessState {
  status: "success";
  data: string[];
}

interface ErrorState {
  status: "error";
  errorMessage: string;
}

type RequestState = LoadingState | SuccessState | ErrorState;

function renderState(state: RequestState): string {
  switch (state.status) {
    case "loading":
      return "Loading...";
    case "success":
      return `Got ${state.data.length} items`; // data is available
    case "error":
      return `Error: ${state.errorMessage}`; // errorMessage is available
  }
}
output
// Each case automatically narrows to the correct interface

Note A discriminated union has a common literal-typed property (the 'discriminant') shared across all members. TypeScript narrows exhaustively in switch/if on that property. This is the recommended pattern for modeling states, events, and message types.