Objects and Interfaces in TypeScript
Introduction
Most application data lives in objects: users, configs, API responses. TypeScript lets you describe object shapes with inline literals, interface contracts, optional and readonly fields, index signatures, and extension. This chapter is the foundation for modeling structured data safely.
Prerequisites
Object Type Literals
Inline shape in a variable or parameter:
const user = {
id: 1,
name: "Sam",
active: true,
};
function printUser(u: { id: number; name: string; active: boolean }): void {
console.log(u.id, u.name, u.active);
}
printUser(user);Code explanation:
- The parameter type lists required properties and their types
- Extra properties on a fresh literal passed directly may error (excess property checking)
interface: Named Object Contract
Reuse and extend shapes with interface:
interface User {
id: number;
name: string;
email: string;
active: boolean;
}
function activate(user: User): User {
return { ...user, active: true };
}Code explanation:
interfacenames a structure you can reference across files- Return type
Userensures the object still matches the contract
Optional Properties
Mark fields that may be missing with ?:
interface Profile {
username: string;
bio?: string;
website?: string;
}
const p: Profile = { username: "dev_ada" };Access optional fields safely:
function bioLength(profile: Profile): number {
return profile.bio?.length ?? 0;
}Code explanation:
bio?meansstring | undefinedwhen reading- Optional chaining avoids runtime errors when
biois absent
Readonly Properties
Prevent reassignment of properties (shallow freeze at type level):
interface Config {
readonly apiUrl: string;
readonly timeoutMs: number;
}
const config: Config = {
apiUrl: "https://api.example.com",
timeoutMs: 5000,
};
// config.apiUrl = "other"; // ErrorReadonly arrays:
interface State {
readonly tags: readonly string[];
}Code explanation:
readonlyapplies to the property binding, not deep immutability of nested objects- For deep readonly, use utility types (later chapter) or careful design
Index Signatures: Dynamic Keys
When keys are not known ahead of time but values share a type:
interface StringMap {
[key: string]: string;
}
const labels: StringMap = {
save: "Save",
cancel: "Cancel",
};
labels["close"] = "Close";Known keys plus index signature:
interface Scores {
math: number;
english: number;
[subject: string]: number;
}Warning
Index Signatures Weaken Safety
Prefer explicit property names when possible. Broad [key: string]: any erases most benefits of typing.
Extending Interfaces
Build larger contracts from smaller ones:
Code explanation:
extendscopies member requirements from the base interface- A value must satisfy all inherited fields
Multiple inheritance:
interface Auditable {
createdBy: string;
}
interface Document extends Timestamped, Auditable {
id: string;
name: string;
}Methods on Interfaces
Code explanation:
- Method syntax in interfaces describes callable members
- Implementations can use method shorthand on object literals
Declaration Merging
Same interface name in the same scope merges declarations:
interface Window {
customFlag?: boolean;
}
interface Window {
customLabel?: string;
}
// Window now has both optional properties (in global augmentation scenarios)This pattern appears in ambient typings extending third-party globals. Avoid merging in application code unless you understand the consequences.
Excess Property Checking
Direct object literals are checked strictly:
interface Point {
x: number;
y: number;
}
function draw(p: Point): void {
console.log(p.x, p.y);
}
draw({ x: 0, y: 0, z: 0 });
// Error: 'z' does not exist in type 'Point'Assigning via a variable often skips excess checks:
const extra = { x: 0, y: 0, z: 0 };
draw(extra);
// May be allowed: 'z' is ignored structurally if not usedCode explanation:
- TypeScript treats fresh literals in calls as strict
- Wider variables assign if they have at least
xandy
Practical Example: API Response Shape
Code explanation:
- Nested interfaces document each layer of the payload
- Literal union
"admin" | "member"restrictsrolevalues (union chapter expands this)
interface vs type Alias (Preview)
This chapter focuses on interface. The next chapter compares interface and type in depth. Rule of thumb: use interface for object shapes you may extend; use type for unions, tuples, and mapped types.
FAQ
Can interfaces describe primitives?
No. Interfaces apply to object shapes. Use type aliases for primitives and unions.
Are interface properties required by default?
Yes, unless marked ? or given in a partial utility type later.
Does interface exist at runtime?
No. Interfaces are erased during compilation—purely compile-time contracts.
Can I implement an interface in a class?
Yes: class UserService implements UserRepository { ... } (classes chapter).
readonly vs const?
const is for variables; readonly is for property assignments on types/classes.
How do I allow extra properties?
Avoid if possible. Use index signatures with a known value type, or Record<string, T> via type aliases.