Capítulo 5 de 36

Chapter 5: Object Types

Core Idea

Every property in an object type carries three independent axes — its type, whether it's optional, and whether it's writable — plus TypeScript layers extra structural machinery on top (index signatures, excess property checks, extends/intersection composition, generics, tuples) for describing the exact shape and mutability contract of real-world objects.

Key Concepts

  • Property modifiers: xPos?: number (optional — reads as T | undefined under strictNullChecks) and readonly prop: string (blocks reassignment during type-checking only, no runtime effect, and does not make nested objects immutable — home.resident.age++ is fine even if resident itself is readonly).
  • readonly is not part of structural compatibility — a Person (mutable) is freely assignable to a ReadonlyPerson variable, and since it's the same underlying object, mutating through the original alias still changes what you read through the readonly-typed one. readonly signals intent, it isn't an immutability guarantee.
  • Destructuring can't carry type annotations inline{ shape: Shape, xPos: number } in a parameter pattern means something different in JS destructuring syntax (renaming), so the type goes after the whole pattern: function f({ shape, xPos }: PaintOptions).
  • Index signatures ([index: string]: T / [index: number]: T): describe dictionary-shaped objects whose exact key set isn't known ahead of time. Only string, number, symbol, template-literal patterns, or unions of those are valid index key types. A string index signature forces every named property to be assignable to the index's value type (name: string conflicts with [index: string]: number) — widen the index to a union (number | string) to allow mixed property types. Index signatures can be readonly.
  • Excess property checks: object literals assigned directly to a typed target are checked for properties the target type doesn't declare — a typo like colour instead of color is a compile error, even though normal structural typing would otherwise allow it (a supertype with extra properties is usually fine). Bypass via a type assertion, a string index signature ([propName: string]: unknown), or by first assigning the literal to an untyped variable (only works if there's still a common property TS can match against).
  • Extending interfaces (interface B extends A { ... }): copies A's members into B, avoiding duplication; interfaces can extend multiple types at once (extends Colorful, Circle).
  • Intersection types (A & B): combine object types via & on a type alias — the main way type composes since it lacks extends. Where interface-merging on a conflicting property name is a hard compile error, an intersection on conflicting property types ({ name: string } & { name: number }) silently collapses that property to never — a value that can now never actually satisfy the type, which is the real key difference between the two composition mechanisms.
  • Generic object types (interface Box<Type> { contents: Type }): a reusable template — Box<string> substitutes Type with string. Avoids the boilerplate of hand-writing StringBox/NumberBox/BooleanBox variants and matching function overloads; a single function setContents<Type>(box: Box<Type>, newContents: Type) covers every instantiation. Type aliases can be generic too (type OrNull<Type> = Type | null), and unlike interfaces can express non-object generics (unions, helper types).
  • Array<Type> is itself a generic interfacestring[] is shorthand for Array<string>. Other built-in generic containers: Map<K, V>, Set<T>, Promise<T>.
  • ReadonlyArray<T> / readonly T[]: no new constructor of its own — created by assigning a regular array to it. Assignability is one-directional: a mutable T[] is assignable to readonly T[], but not the reverse (compile error), unlike the bidirectional readonly property case above.
  • Tuple types ([string, number]): fixed-length, fixed-position arrays with no runtime representation of their own — indexing past the declared length is a compile error. Support optional elements at the end ([number, number, number?], which also affects the inferred length type) and a rest element ([string, number, ...boolean[]], which makes length open-ended). Tuples with rest elements correspond directly to rest-parameter lists, letting a function signature enforce "at least N required args, then any number more of type X."
  • readonly tuples (readonly [string, number]): blocks index writes. as const on an array literal infers a readonly tuple automatically — this is why a [3, 4] as const won't satisfy a plain mutable [number, number] parameter without an explicit cast; the function could mutate it, and TS can't guarantee that won't happen.

Code Examples

interface Box<Type> {
  contents: Type;
}
function setContents<Type>(box: Box<Type>, newContents: Type) {
  box.contents = newContents;
}
  • What it demonstrates: a generic container type + generic function replaces what would otherwise require one interface and one function overload per concrete contents type.
type StringNumberBooleans = [string, number, ...boolean[]];
function readButtonInput(...args: StringNumberBooleans) {
  const [name, version, ...input] = args;
}
  • What it demonstrates: a tuple-with-rest type used as a rest-parameter annotation to require a fixed prefix (name: string, version: number) plus any number of trailing booleans.

Reference Tables

ConstructComposition keywordConflict behavior
interface extendsextendssame-name incompatible properties → hard error
Intersection (type)&same-name incompatible properties → property silently becomes never
Index signature[k: string]: Tevery named property must be assignable to T (union T to allow mixed)

Key Takeaways

  1. readonly and ReadonlyArray/readonly T[] are compile-time-only signals of intent, not runtime immutability — mutation through an aliased mutable reference still works.
  2. Prefer interface extends when composing plain object shapes (clearer error messages, catches genuine property conflicts); reach for intersection types when at least one side isn't a plain object type, keeping in mind conflicting properties resolve to never instead of erroring.
  3. Excess property checks only fire on object literals assigned/passed directly — route through an intermediate variable (when there's a shared property) or add an index signature if an "extra fields allowed" API is intentional.
  4. Prefer readonly tuples/arrays by default for data that's created once and not mutated — as const gets you there for free on literals.

Connects To

  • Everyday Types: optional properties, union types, and as const first introduced there, used more deeply here.
  • More on Functions: generic function guidelines that pair with generic object types like Box<Type>.
  • Type Manipulation / Generics: constraints, defaults, and more advanced generic patterns beyond the basics shown here.
  • Mapped Types: how to strip/add readonly and ? modifiers programmatically across every property of a type.