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| Use | Interface | Type alias |
|---|---|---|
| Object shape | Yes | Yes |
| Union or tuple | No | Yes |
| Declaration merging | Yes - the same name can be reopened | No |
| Extending something | extends | & intersection |
| Implemented by a class | Yes | Yes, when it is an object shape |
| Default for public APIs | Yes - clearer errors and extendable | For 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.Related
Basic types and inference tsconfig and tooling
Last refreshed 2026-09-18.