In simple language, an interface describes the shape of an object — what properties it has and what types they are. It’s a contract: “anything that wants to be a User must have these fields”.
interface User {
id: number;
name: string;
email: string;
}
const u: User = { id: 1, name: "Manish", email: "m@x.com" };
Optional and readonly
? marks a property as optional. readonly makes it immutable after creation — assignment after init is a compile error.
interface Post {
readonly id: number; // can't reassign after creation
title: string;
draft?: boolean; // may or may not exist
}
const p: Post = { id: 1, title: "Hi" };
p.title = "Hey"; // OK
p.id = 2; // Error — readonly
readonly is shallow — nested objects are still mutable.
Extending interfaces
One interface can extend another (or multiple). Think of it like the only difference is we’re stacking shapes.
interface Animal {
name: string;
}
interface Dog extends Animal {
breed: string;
}
interface ServiceDog extends Dog, Trainable {
serviceType: string;
}
interface Trainable {
trainerId: number;
}
Index signatures
When we don’t know all the keys ahead of time (think: a dictionary), we use an index signature. The key can be string, number, or symbol.
interface Translations {
[locale: string]: string;
}
const t: Translations = {
en: "Hello",
hi: "Namaste",
};
t.es = "Hola"; // OK — any string key works
We can also mix named props with an index signature, but the named props must be assignable to the index value type.
interface Config {
version: string; // known
[key: string]: string; // anything else is also string
}
Interface vs type alias
Both can describe object shapes. Key differences:
- Interfaces can be re-opened (declaration merging) — handy for extending library types.
- Type aliases support unions, intersections, mapped types, conditional types.
interface Box { width: number; }
interface Box { height: number; } // merged!
// Box now requires { width, height }
type Status = "ok" | "fail"; // union — only type can do this
Rule of thumb: use interface for object shapes meant to be extended/implemented, type for unions, primitives, and computed types.
Implementing interfaces
Classes use implements to promise they match the shape. We’ll see this in the abstract classes note.
interface Printable {
print(): void;
}
class Doc implements Printable {
print() { console.log("printing..."); }
}
Interview gotcha
Excess property checks: object literals get extra-strict checks, but variables don’t. { id: 1, name: "x", extra: true } as User triggers an error, but assigning a variable with that shape doesn’t. This trips people up.