Handbooks / TypeScript / Chapter 2
Interfaces, Types and Unions
7 pages · ~5 min✓ Reviewed
Builds on Basic Types. Next up: Narrowing.
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 } interfaceandtypeboth 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 missingextra 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.
Interfaces vs Type Aliases
1. Same Shape, Two Syntaxes
interface User { id: number }declares an object shapetype 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
| interface | type | |
|---|---|---|
| Extend | extends | & |
| Same name twice | merges | error |
| Union, primitive, tuple | no | yes |
| Mapped, conditional | no | yes |
- Two
interface Userblocks merge into one - A duplicate
typealias is an error
typescript
interface User { id: number }
interface User { name: string }
const u: User = { id: 1, name: 'Ann' }
type ID = string | numbermerged User needs both members
3. Rule of Thumb
Which one?
interface for public object and class contracts. type for anything else.
Optional and Readonly Properties
1. Optional properties
nickname?: stringhas type string | undefined- The key may be omitted entirely
?is not the same as nullable
| Type | Key required? | undefined OK? |
|---|---|---|
{ a?: string } | no | yes |
{ a: string | undefined } | yes | yes |
2. readonly
readonly id: numberblocks reassignmentreadonlyis shallow: contents oftagsstay mutable
typescript
interface User {
nickname?: string;
readonly id: number;
readonly tags: string[];
}
user.id = 2; // error: read-only
user.tags.push('x'); // allowedshallow: only the property is locked
- Lock contents:
ReadonlyArray<string>orreadonly string[]
3. Utility types
| Utility | Effect |
|---|---|
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.
Unions, Intersections and Literal Types
1. Unions and Intersections
| Value is | Access | |
|---|---|---|
A | B | one of A, B | common members only |
A & B | all of A and B | every member |
- Narrow with
typeof,in,instanceof,===iniforswitch - Conflicting property types in
&collapse to never
2. Literal Types
type Dir = 'up' | 'down'allows only those exact valuesconstkeeps the literal,letwidens tostring
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.
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 // OK2. Excess Property Checks
| Source | Code | Result |
|---|---|---|
| Fresh literal | const u: User = { id: 1, extra: 2 } | error |
| Via variable | const u: User = o | allowed |
- 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 // errorRemember
Fresh literal ⇒ excess check. Same object via a variable ⇒ only structural fit.
Cheat Sheet: Object Types
1. Interface vs Type
| interface | type | |
|---|---|---|
| Extend | extends | & |
| Merge | yes, same name | error |
| Union, tuple, mapped | no | yes |
2. Properties and Unions
prop?: Tmeans T | undefined, may be omittedreadonly prop: Tis shallow and compile-time onlyA | B= one of;A & B= all of- Discriminate unions with a literal
kindfield, thenswitch
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.
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
ais still required - Only
a?: stringlets you omit the key
With readonly tags: string[], does user.tags.push('x') compile?
- Yes,
readonlyis shallow user.tags = []is the error- Use
readonly string[]to blockpush