Either type

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.

Result Type Pattern

TS · Common Patterns
syntax
type Result<T, E = Error> =
  | { ok: true; value: T }
  | { ok: false; error: E };
example
type Result<T, E = string> =
  | { ok: true; value: T }
  | { ok: false; error: E };

function parseAge(input: string): Result<number> {
  const num = parseInt(input, 10);
  if (isNaN(num)) return { ok: false, error: "Not a valid number" };
  if (num < 0 || num > 150) return { ok: false, error: "Age out of range" };
  return { ok: true, value: num };
}

const result = parseAge("25");
if (result.ok) {
  console.log(`Age: ${result.value}`); // narrowed to { ok: true; value: number }
} else {
  console.log(`Invalid: ${result.error}`); // narrowed to { ok: false; error: string }
}
output
// Discriminated union forces callers to handle both success and failure

Note The Result pattern replaces throwing exceptions with explicit return types. Callers cannot forget to handle the error case because TypeScript requires narrowing before accessing .value or .error. This is inspired by Rust's Result<T, E> type.