Interfaces

intermediate interfaces types objects

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.