Handbooks / TypeScript / Chapter 2

Interfaces, Types and Unions

7 pages · ~5 min✓ Reviewed

Builds on Basic Types. Next up: Narrowing.

TypeScriptPage 1

Object Types Mental Model

1. Structural Typing

  • A value fits if it has the required members; declared name or class is irrelevant
  • Object type = required members + their types: { id: number; name: string }
  • interface and type both name a shape; one variable satisfies either

2. Assignability Is Directional

typescript
interface User { id: number; name: string }
type Named = { name: string }
const u: User = { id: 1, name: "Ann" }
const n: Named = u // wider to narrower: OK
const back: User = n // error: id missing

extra members are fine, missing ones are not

  • Wider (extra members) is assignable to narrower
  • Narrower to wider is an error: members missing

3. Runtime Reality

  • Types are erased at compile time; no runtime check exists
  • Validate external data separately
Remember

as User on parsed JSON checks nothing; it only silences the compiler.

TypeScriptPage 2

Interfaces vs Type Aliases

1. Same Shape, Two Syntaxes

  • interface User { id: number } declares an object shape
  • type User = { id: number } does the same
  • interface: interface Admin extends User { role: string }
  • alias: type Admin = User & { role: string } (intersection)

2. What Only One Can Do

interfacetype
Extendextends&
Same name twicemergeserror
Union, primitive, tuplenoyes
Mapped, conditionalnoyes
  • Two interface User blocks merge into one
  • A duplicate type alias is an error
typescript
interface User { id: number }
interface User { name: string }
const u: User = { id: 1, name: 'Ann' }
type ID = string | number

merged User needs both members

3. Rule of Thumb

Which one?

interface for public object and class contracts. type for anything else.

TypeScriptPage 3

Optional and Readonly Properties

1. Optional properties

  • nickname?: string has type string | undefined
  • The key may be omitted entirely
  • ? is not the same as nullable
TypeKey required?undefined OK?
{ a?: string }noyes
{ a: string | undefined }yesyes

2. readonly

  • readonly id: number blocks reassignment
  • readonly is shallow: contents of tags stay mutable
typescript
interface User {
  nickname?: string;
  readonly id: number;
  readonly tags: string[];
}
user.id = 2; // error: read-only
user.tags.push('x'); // allowed

shallow: only the property is locked

  • Lock contents: ReadonlyArray<string> or readonly string[]

3. Utility types

UtilityEffect
Readonly<T>all properties readonly
Partial<T>all properties optional
Compile-time only

readonly is erased at runtime. It stops assignments in your code, not mutation of the real object.

TypeScriptPage 4

Unions, Intersections and Literal Types

1. Unions and Intersections

Value isAccess
A | Bone of A, Bcommon members only
A & Ball of A and Bevery member
  • Narrow with typeof, in, instanceof, === in if or switch
  • Conflicting property types in & collapse to never

2. Literal Types

  • type Dir = 'up' | 'down' allows only those exact values
  • const keeps the literal, let widens to string
typescript
const x = 'up'; // type 'up'
let y = 'up';   // type string

3. Discriminated Unions

typescript
type Shape =
  | { kind: 'circle'; r: number }
  | { kind: 'square'; s: number };
switch (sh.kind) {
  case 'circle': return sh.r;
  case 'square': return sh.s;
  default: const _x: never = sh;
}

shared literal kind narrows each case

Exhaustiveness

A never default stops compiling when a new kind is added.

TypeScriptPage 5

Structural Typing and Excess Property Checks

1. Structural Typing

  • Identical structure ⇒ interchangeable
  • A different name does not make types incompatible
typescript
type A = { id: number }
interface B { id: number }
const a: A = { id: 1 }
const b: B = a  // OK

2. Excess Property Checks

SourceCodeResult
Fresh literalconst u: User = { id: 1, extra: 2 }error
Via variableconst u: User = oallowed
  • Checked on fresh object literals only
  • Bypass: type assertion as User
  • Opt in: index signature [key: string]: unknown

3. Weak Types and Brands

  • Weak type = all properties optional
  • Value sharing none of them is an error
  • Brand types that share a shape but must not mix
typescript
type UserId = string & { __brand: 'UserId' }
type OrderId = string & { __brand: 'OrderId' }
const u = 'u1' as UserId
const o: OrderId = u  // error
Remember

Fresh literal ⇒ excess check. Same object via a variable ⇒ only structural fit.

TypeScriptPage 6

Cheat Sheet: Object Types

1. Interface vs Type

interfacetype
Extendextends&
Mergeyes, same nameerror
Union, tuple, mappednoyes

2. Properties and Unions

  • prop?: T means T | undefined, may be omitted
  • readonly prop: T is shallow and compile-time only
  • A | B = one of; A & B = all of
  • Discriminate unions with a literal kind field, then switch

3. Literals

typescript
const a = 'up';  // 'up'
let b = 'up';    // string
const o = { k: 'up' } as const;
// { readonly k: 'up' }

as const freezes to literal readonly types

4. Excess Property Check

  • Runs on fresh literals only
  • Via a variable: structural compatibility, extras allowed
Remember

readonly is shallow: tags.push() still works. Types are erased at runtime.

TypeScriptPage 7

Check yourself

With type User = { id: number }, does const o = { id: 1, extra: 2 }; const u: User = o; compile?
  • Yes, it compiles
  • Excess property checks hit only fresh object literals
  • Writing { id: 1, extra: 2 } directly in the assignment would error
Does const x: { a: string | undefined } = {}; compile?
  • No, the key a is still required
  • Only a?: string lets you omit the key
With readonly tags: string[], does user.tags.push('x') compile?
  • Yes, readonly is shallow
  • user.tags = [] is the error
  • Use readonly string[] to block push