Interfaces, type aliases and generics

When to use an interface rather than a type alias, how structural typing behaves, and what generics actually buy you.

Describing shapes

interface User {
  id: number;
  name: string;
  email?: string;              // optional member
  readonly createdAt: Date;    // cannot be reassigned after construction
}

interface Admin extends User {
  permissions: string[];
}

type Point = { x: number; y: number };          // an object shape
type Id = string | number;                      // a union, not a shape
type Handler = (event: Event) => void;          // a function type
type UserWithCount = User & { posts: number };  // an intersection
UseInterfaceType alias
Object shapeYesYes
Union or tupleNoYes
Declaration mergingYes - the same name can be reopenedNo
Extending somethingextends& intersection
Implemented by a classYesYes, when it is an object shape
Default for public APIsYes - clearer errors and extendableFor everything a shape cannot express

TypeScript is structurally typed: a value matches a type when it has the required members, no matter where it came from. The names are documentation for humans, not identity at runtime.

Generics

function first<T>(items: T[]): T | undefined {
  return items[0];
}
const number = first([1, 2, 3]);     // number | undefined

// constraints keep the generic useful instead of powerless
function pluck<T, K extends keyof T>(items: T[], key: K): T[K][] {
  return items.map(item => item[key]);
}
pluck(users, 'id');                  // number[]
// pluck(users, 'nope');             // error: not a key of User

// generic on a type alias
type ApiResult<T> = { data: T; status: number };

async function get<T>(url: string): Promise<ApiResult<T>> {
  const response = await fetch(url);
  return { data: (await response.json()) as T, status: response.status };
}
💡
Generics let the compiler carry a type through a function so you do not repeat it at every call site. If you find yourself writing casts, an unconstrained type parameter is usually the real problem - the assertion only silences the symptom.

FAQ

Interface or type alias?
Either works for object shapes. Prefer interface for anything a consumer might extend or implement, and type for unions, tuples and mapped types. Consistency in a codebase matters more than the choice.
Why does my function return an unexpectedly wide type?
An unconstrained type parameter or a missing return annotation widened it. Add extends constraints and annotate the exported return type so inference stays narrow.

Basic types and inference tsconfig and tooling

Last refreshed 2026-09-18.