Skip to main content
CodeOath
← All posts

TypeScript63 min total · 21 parts

TypeScript Fundamentals: Types, Interfaces, Generics, and Why It Catches Bugs Before Runtime

Part 5 of 21 · ~2 min

interface vs. type

Model the same applicant record two ways:

interface Applicant {
  id: number;
  name: string;
  email: string;
}

type ApplicantT = {
  id: number;
  name: string;
  email: string;
};

Nothing about how you'd use either version differs. This equivalence is exactly why "what's actually different between these two" shows up in so many TypeScript interviews — the honest answer is "not much, for a shape like this one," followed by a short list of places they genuinely diverge:

interfacetype
Describing an object's shapeYesYes
A union ("a" | "b")NoYes
A bare primitive or tupleNoYes
Building on another shapeextends&
Reopening the same name laterAllowed, and additiveA hard error

That last row has real behavioral teeth. Declare an interface with a name that's already taken in the same scope, and TypeScript doesn't complain — it folds the new fields into the existing declaration:

// reviewer.ts
interface ReviewSession {
  applicantId: number;
}

// scoring.ts — a different file, written by a different person
interface ReviewSession {
  reviewerId: number;
}

// from here on, ReviewSession requires BOTH applicantId and reviewerId

This is genuinely useful on purpose — it's how a library lets you bolt your own fields onto a type it doesn't own, like Express's Request or the browser's global Window, without touching that library's source. It's also exactly how two people on the same team step on each other by accident: each one writes interface ReviewSession expecting a private, self-contained shape, and TypeScript quietly merges the two into one combined type instead of flagging the collision. An object built to satisfy reviewer.ts's version is now missing reviewerId as far as scoring.ts is concerned — and neither file's own declaration shows any sign that something went wrong.