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.
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"; // Errorbio may be omitted, name can change, and id cannot be reassigned through this reference. readonly is a type-system restriction, not Object.freeze.- Use optional properties only when absence is valid.
readonlyprevents assignment through a typed reference.- Model mutually exclusive states with unions when appropriate.
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 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.- 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.
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.
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"
};Feature union requires all three keys. The string-keyed record allows dictionary-style entries, but reading a missing key may still need runtime handling.- Use
Recordfor mapped key/value objects. - Finite key unions can enforce complete dictionaries.
- Consider
noUncheckedIndexedAccessfor safer indexed reads.
What does `Record<"a" | "b", number>` require?