3 Topics
Chapter•65 Minutes

Object Types, Type Aliases, and Interfaces

Define object shapes, optional and readonly properties, index signatures, interfaces, aliases, and composition.

Object Shapes, Optional, and Readonly Properties

Object types define the properties and their types. A ? marks a property as optional, which means it may be absent (and under strict null checks, reading it can produce undefined). readonly prevents assignment through that particular typed reference, but it is not a deep runtime freeze and does not guarantee that another alias cannot mutate the same object.

Use optional properties when absence is meaningful. If a value has distinct states with different required fields, a discriminated union is often clearer than one object containing many optional fields.

A typed settings object
type Profile = {
  readonly id: string;
  name: string;
  bio?: string;
};

const profile: Profile = { id: "p-1", name: "Mira" };
profile.name = "Mira S.";
// profile.id = "p-2"; // Error
💡 bio may be omitted, name can change, and id cannot be reassigned through this reference. readonly is a type-system restriction, not Object.freeze.
Key Takeaways
  • Use optional properties only when absence is valid.
  • readonly prevents assignment through a typed reference.
  • Model mutually exclusive states with unions when appropriate.
Knowledge Check

What does `bio?: string` mean?

Interfaces vs Type Aliases

Both interface and type can describe object shapes and are often interchangeable for ordinary object contracts. Interfaces support declaration merging and can be extended with extends. Type aliases can name unions, intersections, primitives, tuples, and mapped or conditional types. Pick the form that communicates intent and fits the library's extension model.

Avoid arguing that one is universally better. Public library APIs sometimes use interfaces for extendable object contracts; aliases are essential for unions and type-level transformations. Keep naming consistent within a codebase.

Interface and type alias
interface Identified {
  id: string;
}

interface Customer extends Identified {
  email: string;
}

type ApiStatus = "idle" | "loading" | "done";
💡 Customer extends an object interface. ApiStatus is a union, which is naturally represented by a type alias.
Key Takeaways
  • Both can describe object shapes.
  • Interfaces can merge declarations; aliases can represent unions and other type expressions.
  • Consistency and the intended extension model matter more than dogma.
Knowledge Check

Which construct is required to name a union such as `"open" | "closed"`?

Index Signatures and Record

An index signature describes objects whose keys follow a pattern and whose values share a type. Record<K, V> is a utility type that maps a key type K to a value type V. Use finite key unions with Record when every known key should be present; use index signatures for dictionary-like structures where arbitrary keys are allowed.

Be cautious with dictionary access: a key may not exist at runtime. noUncheckedIndexedAccess can make indexed reads include undefined, encouraging safer handling. Avoid mixing arbitrary values into a typed object unless the domain genuinely supports them.

Typed dictionary
type Feature = "search" | "billing" | "reports";
const enabled: Record<Feature, boolean> = {
  search: true,
  billing: false,
  reports: true
};

const labels: Record<string, string> = {
  ready: "Ready",
  failed: "Failed"
};
💡 The finite Feature union requires all three keys. The string-keyed record allows dictionary-style entries, but reading a missing key may still need runtime handling.
Key Takeaways
  • Use Record for mapped key/value objects.
  • Finite key unions can enforce complete dictionaries.
  • Consider noUncheckedIndexedAccess for safer indexed reads.
Knowledge Check

What does `Record<"a" | "b", number>` require?