Lesson 3 of 13 · 0 of 2 exercises solved

Interfaces & Type Aliases

and type aliases both let you give a name to the shape of an object. This lesson shows how to use each and the small differences between them.

// Interface
interface User {
  id: number
  name: string
  email?: string        // optional property
  readonly createdAt: Date  // can't be reassigned after creation
}

const user: User = {
  id: 1,
  name: "Alice",
  createdAt: new Date(),
}

// user.createdAt = new Date()   // Error: readonly

// Extending interfaces
interface AdminUser extends User {
  role: "admin" | "superadmin"
  permissions: string[]
}

// Type alias — can do everything interfaces can, plus more
type Point = {
  x: number
  y: number
}

// Type aliases can represent primitives, unions, intersections
type ID      = string | number
type Status  = "active" | "inactive" | "pending"  // literal union type

// Intersection types — combine multiple types
type TimestampedUser = User & {
  updatedAt: Date
  version: number
}

const admin: AdminUser = {
  id: 2,
  name: "Bob",
  createdAt: new Date(),
  role: "admin",
  permissions: ["read", "write", "delete"],
}
TypeScript (compiler loads on first run)Ctrl+Enter runs
// Index signatures — object with dynamic keys
interface StringMap {
  [key: string]: string
}

const config: StringMap = {
  host: "localhost",
  port: "3000",
  env: "development",
}

// Function types in interfaces
interface Transformer<T, U> {
  (input: T): U
}

const double: Transformer<number, number> = (n) => n * 2
const stringify: Transformer<number, string> = (n) => String(n)

// Discriminated unions — tagged union types
type Shape =
  | { kind: "circle";    radius: number }
  | { kind: "rectangle"; width: number; height: number }
  | { kind: "triangle";  base: number;  height: number }

function area(shape: Shape): number {
  switch (shape.kind) {
    case "circle":
      return Math.PI * shape.radius ** 2
    case "rectangle":
      return shape.width * shape.height
    case "triangle":
      return 0.5 * shape.base * shape.height
  }
  // TypeScript knows all cases are handled — no default needed
}

console.log(area({ kind: "circle", radius: 5 }))     // 78.54...
console.log(area({ kind: "rectangle", width: 4, height: 6 }))  // 24
TypeScript (compiler loads on first run)Ctrl+Enter runs

Practice

Define an interface Person with name (string), age (number), optional skills (string ) and a readonly id (number). Then write describe(p: Person) returning "Maya (34): design, cooking" — or "Maya (34): no skills listed" when skills are missing.

Exercise

An interface with optional and readonly fields

4 checks
// interface Person { ... }

function describe(p) {
  return ""
}

console.log(describe({ id: 1, name: "Maya", age: 34, skills: ["design", "cooking"] }))
console.log(describe({ id: 2, name: "Sam", age: 29 }))
 
TypeScript (compiler loads on first run)Ctrl+Enter runs
Output
Press Run to try your code, then Check answer to grade it.

Model a payment with a discriminated union: type Payment is either { kind: "card"; last4: string } or { kind: "cash" } or { kind: "transfer"; iban: string }. Write label(p: Payment) that returns "Card ending 4242", "Cash" or "Transfer to DE89…" (first four characters of the IBAN followed by an ellipsis).

Exercise

A discriminated union

4 checks
// type Payment = ...

function label(p) {
  return ""
}

console.log(label({ kind: "card", last4: "4242" }))
console.log(label({ kind: "cash" }))
console.log(label({ kind: "transfer", iban: "DE89370400440532013000" }))
 
TypeScript (compiler loads on first run)Ctrl+Enter runs
Output
Press Run to try your code, then Check answer to grade it.

Check yourself

Quick check

When should you pick a type alias over an interface?

Finished reading?2 exercises still open. You can come back to them later.

Tip: use the ← and → keys to move between lessons.