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:

typescript
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:

typescript
interface User {
  id: number;
  name: string;
  email: string;
  active: boolean;
}
 
function activate(user: User): User {
  return { ...user, active: true };
}

Code explanation:

  • interface names a structure you can reference across files
  • Return type User ensures the object still matches the contract

Optional Properties

Mark fields that may be missing with ?:

typescript
interface Profile {
  username: string;
  bio?: string;
  website?: string;
}
 
const p: Profile = { username: "dev_ada" };

Access optional fields safely:

typescript
function bioLength(profile: Profile): number {
  return profile.bio?.length ?? 0;
}

Code explanation:

  • bio? means string | undefined when reading
  • Optional chaining avoids runtime errors when bio is absent

Readonly Properties

Prevent reassignment of properties (shallow freeze at type level):

typescript
interface Config {
  readonly apiUrl: string;
  readonly timeoutMs: number;
}
 
const config: Config = {
  apiUrl: "https://api.example.com",
  timeoutMs: 5000,
};
 
// config.apiUrl = "other";  // Error

Readonly arrays:

typescript
interface State {
  readonly tags: readonly string[];
}

Code explanation:

  • readonly applies 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:

typescript
interface StringMap {
  [key: string]: string;
}
 
const labels: StringMap = {
  save: "Save",
  cancel: "Cancel",
};
 
labels["close"] = "Close";

Known keys plus index signature:

typescript
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:

  • extends copies member requirements from the base interface
  • A value must satisfy all inherited fields

Multiple inheritance:

typescript
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:

typescript
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:

typescript
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:

typescript
const extra = { x: 0, y: 0, z: 0 };
draw(extra);
// May be allowed: 'z' is ignored structurally if not used

Code explanation:

  • TypeScript treats fresh literals in calls as strict
  • Wider variables assign if they have at least x and y

Practical Example: API Response Shape

Code explanation:

  • Nested interfaces document each layer of the payload
  • Literal union "admin" | "member" restricts role values (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.