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"],
}// 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 })) // 24Practice
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.
An interface with optional and readonly fields
// 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 }))
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).
A discriminated union
// 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" }))
Press Run to try your code, then Check answer to grade it.Check yourself
When should you pick a type alias over an interface?
Tip: use the ← and → keys to move between lessons.