Object Types
Why object types matter
Most real-world TypeScript code is about objects: API responses, database records, configuration objects, UI props, and domain models. Object types define the shape of data and prevent the most common production failures: missing fields, incorrect types, and unsafe assumptions when data evolves.
Basic object type syntax
You can type an object by defining its required properties and their types.
let user: { id: number; name: string } = {
id: 1,
name: "Yusuf"
};
If a required property is missing or has the wrong type, TypeScript reports an error at compile time.
Optional properties
Not every field exists in every scenario. Use ? for optional properties. This forces you to handle the absence of the value safely.
type Profile = {
id: number;
name: string;
avatarUrl?: string;
};
const p: Profile = { id: 1, name: "Elif" };
Readonly properties
Some properties should never change after creation, such as database primary keys. Use readonly to prevent accidental mutation.
type User = {
readonly id: number;
name: string;
};
const u: User = { id: 1, name: "Yusuf" };
// u.id = 2; // Error
u.name = "Yusuf A."; // OK
Nested object structures
Production data is often nested. Object types can model nested structures clearly and safely.
type Post = {
id: number;
title: string;
author: {
id: number;
name: string;
};
};
const post: Post = {
id: 10,
title: "Type Safety",
author: { id: 1, name: "Ozan" }
};
Index signatures for dynamic keys
Sometimes objects act like dictionaries where keys are not known ahead of time. Index signatures allow safe dynamic access.
type StringMap = {
[key: string]: string;
};
const headers: StringMap = {
"content-type": "application/json",
"accept": "application/json"
};
Use index signatures carefully. They can make models too permissive if overused.
Excess property checks
TypeScript protects you from accidentally passing extra properties when using object literals. This is valuable for catching typos and API contract drift.
type Config = { debug: boolean };
const cfg: Config = { debug: true, debg: true };
// Error: 'debg' does not exist in type Config
Objects as contracts between layers
In production systems, object types should represent contracts between layers: controllers, services, database access, and UI components. Keeping these contracts explicit makes refactoring and collaboration significantly safer.
Prefer named types for reusable models
Inline object types are fine for small examples, but reusable models should be defined as named types (type aliases or interfaces). This improves readability and prevents duplication.
Common mistakes
- Marking too many fields as optional and losing safety.
- Using broad index signatures that hide invalid keys.
- Using
anyfor objects instead of modeling structure. - Mixing domain models and transport models without clear boundaries.
Production guidance
- Model required fields explicitly and keep optional fields meaningful.
- Use
readonlyfor identifiers and immutable properties. - Use named types for shared contracts across files.
- Keep dictionary-like objects constrained and intentional.
What’s next
Now that you can model object shapes safely, the next step is typing functions more deeply: parameters, return types, and patterns for predictable APIs.