Interfaces, Type Aliases and Structural Typing
IntermediateTypeScript compares shapes, not names. Two unrelated types with the same members are interchangeable — the opposite of how Java works.
Overview
Java is nominally typed: a class implements an interface only if it says so. TypeScript is structurally typed: if an object has the required members, it fits, regardless of where it came from or what it is called. This is the single biggest mental adjustment for a backend engineer, and it is what makes TypeScript pleasant to use with plain object literals and third-party data. The related question — interface or type alias — matters much less than people argue; both describe object shapes, and the practical differences are narrow.
Structural Typing
Shape is identity. Nothing needs to declare a relationship.
interface Point { x: number; y: number }
class Vector { constructor(public x: number, public y: number) {} }
function length(p: Point) { return Math.hypot(p.x, p.y) }
length(new Vector(3, 4)) // fine — Vector has x and y
length({ x: 3, y: 4 }) // fine — the literal has x and y
// In Java, Vector would have to declare 'implements Point'.
// Extra properties are allowed... except on fresh object literals
const v = { x: 3, y: 4, z: 5 }
length(v) // OK — v is a variable
length({ x: 3, y: 4, z: 5 }) // Error — excess property check
// The literal check exists to catch typos in options objects.interface vs type
Both describe objects. The differences that actually come up.
interface User { id: number; name: string }
type User2 = { id: number; name: string } // equivalent
// Extension
interface Admin extends User { role: string }
type Admin2 = User2 & { role: string } // intersection
// Only 'type' can express these
type Id = string | number // unions
type Pair = [string, number] // tuples
type Getter = () => User // (interfaces can too, awkwardly)
type Keys = keyof User // mapped/computed types
// Only 'interface' merges declarations
interface Window { myApp: App } // adds to the existing Window
// which is how you augment third-party or global types
// In practice: 'interface' for object shapes you may extend or augment,
// 'type' for everything else. Being consistent matters more than the choice.Modifiers and Index Signatures
Optional, readonly, and typing objects with dynamic keys.
interface Problem {
readonly id: string // assignable only at creation
title: string
hint?: string // string | undefined
tags: readonly string[] // no push, no mutation
}
// Index signature — unknown keys, known value type
interface Scores { [topic: string]: number }
const s: Scores = { arrays: 80, graphs: 65 }
// Record is the same thing, more readable
type Scores2 = Record<string, number>
// With a closed key set, prefer this — it catches missing keys
type Level = 'easy' | 'medium' | 'hard'
type Counts = Record<Level, number>
const counts: Counts = { easy: 1, medium: 2 } // Error: 'hard' is missing
// readonly is compile-time only — Object.freeze is the runtime versionKey Points to Remember
- 1TypeScript is structurally typed — a value fits a type if it has the right shape, with no declared relationship
- 2Fresh object literals get an excess-property check that variables do not, which catches option typos
- 3Only type aliases can express unions, tuples and mapped types; only interfaces merge across declarations
- 4readonly and optional are compile-time only — Object.freeze is the runtime equivalent
- 5Record<Level, number> over an index signature when the keys are a known set, because missing keys are then errors
Interview Questions
Sign in to ask AriaWhat is structural typing and how does it differ from Java's nominal typing?
When must you use a type alias rather than an interface?
Why does passing an object literal with an extra property fail, when passing a variable with the same extra property succeeds?
Ask Aria about Interfaces, Type Aliases and Structural Typing
Your personal AI tutor — ask anything about this concept
Revision Status
Personal Notes
Sign in to save personal notes for this topic.
Discussion
Sign in to join the discussion.