Skip to main content

TS Ep 4: Object Types, Interfaces, and Structural Typing

Rachmat Hidayat
Author
Rachmat Hidayat
Learn & sharing insights on TypeScript, Go, Kubernetes, DevOps, DevSecOps, SRE, Platform Engineering, AI/ML Engineering, and MLOps.
typescript - This article is part of a series.
Part 4: This Article
JavaScript is built on dynamic objects. In TypeScript, object types define the structural shape of your data. Understanding structural typing, excess property checks, optional properties, and immutability is essential for building safe object models.

1. Defining Object Types: Inline, Type, and Interface
#

There are three ways to define an object’s shape in TypeScript:

1. Inline Object Types
#

Useful for small, one-off function parameters, but hard to reuse across a codebase.

function printUser(user: { name: string; age: number }) {
  console.log(`${user.name} is ${user.age} years old.`);
}

2. Type Aliases (type)
#

Allows saving an object shape under an alias name. Supports primitives, unions, and tuples.

type User = {
  name: string;
  age: number;
};

3. Interfaces (interface)
#

Designed specifically for defining object contracts and class interfaces. Supports interface merging and inheritance (extends).

interface UserInterface {
  name: string;
  age: number;
}

2. Structural Typing (Duck Typing)
#

TypeScript utilizes a Structural Type System (often described as static “duck typing”).

In nominal languages like Java or C#, a class User is only compatible with User if it explicitly declares that relationship. In TypeScript, compatibility is determined solely by shape.

If two objects share the required properties with compatible types, TypeScript considers them the same type, regardless of how or where they were defined.

interface Point2D {
  x: number;
  y: number;
}

interface Vector2D {
  x: number;
  y: number;
}

function logCoordinates(point: Point2D) {
  console.log(`X: ${point.x}, Y: ${point.y}`);
}

const myVector: Vector2D = { x: 10, y: 20 };

// 🟢 WORKED! Vector2D has the exact same structure as Point2D!
logCoordinates(myVector); 

Extra Properties in Structural Typing
#

If an object has more properties than required by the target type, TypeScript still permits it when assigned via an intermediary variable:

const point3D = { x: 1, y: 2, z: 3 };

// Allowed! point3D satisfies Point2D because it has 'x' and 'y' of type number.
logCoordinates(point3D); 

3. Excess Property Checking
#

While structural typing allows objects with extra properties, TypeScript applies a special rule called Excess Property Checking when an object literal is passed directly inline.

// ❌ Compiler Error: Object literal may only specify known properties, 
// and 'z' does not exist in type 'Point2D'.
// logCoordinates({ x: 1, y: 2, z: 3 });

Why Excess Property Checks Exist
#

Passing { x: 1, y: 2, z: 3 } directly inline is almost always a developer typo. If you pass an inline object literal, TypeScript assumes you intended to match the type exactly, and flags any extraneous keys as errors.

To bypass excess property checks, assign the object literal to a temporary variable first:

const payload = { x: 1, y: 2, z: 3 };
logCoordinates(payload); // Allowed! (Triggers structural check, not excess check)

4. Optional Properties vs undefined
#

You mark a property as optional by placing a question mark (?) before the colon:

interface UserProfile {
  id: string;
  email: string;
  bio?: string; // Optional property
}

The Critical Difference: bio?: string vs bio: string | undefined
#

DeclarationCan Omit Property Key?Valid Values
bio?: stringYES"hello", undefined, or omit { id: "1", email: "[email protected]" }
bio: string | undefinedNO"hello", undefined (Key MUST be explicitly present!)
// Valid for bio?: string
const user1: UserProfile = { id: "1", email: "[email protected]" };

type StrictBio = {
  bio: string | undefined;
};

// ❌ Error: Property 'bio' is missing in type '{ ... }' but required in type 'StrictBio'.
// const user2: StrictBio = {}; 

const user2: StrictBio = { bio: undefined }; // Valid!

5. readonly Properties & Immutability
#

The readonly modifier prevents property reassignment after object initialization.

interface Account {
  readonly accountId: string;
  balance: number;
}

const account: Account = {
  accountId: "acc_99812",
  balance: 5000,
};

account.balance = 5500; // Valid!

// ❌ Compiler Error: Cannot assign to 'accountId' because it is a read-only property.
// account.accountId = "acc_00000"; 
Note

readonly is compile-time enforcement only. It does not prevent mutation at runtime in compiled JavaScript unless combined with Object.freeze().


6. Index Signatures
#

When an object can contain arbitrary keys (like a dictionary or lookup map), use an Index Signature:

interface FeatureFlags {
  // Allows any string key mapped to a boolean value
  [flagName: string]: boolean;
}

const flags: FeatureFlags = {
  enableNewUI: true,
  enableBetaCheckout: false,
  darkTheme: true,
};

const isBetaEnabled = flags["enableBetaCheckout"]; // Inferred Type: boolean

You can also combine fixed properties with index signatures, provided the fixed properties match the index signature return type:

interface ConfigMap {
  env: string; // Fixed property
  [key: string]: string; // Index signature matching string values
}

7. Extending Interfaces (extends)
#

Interfaces can inherit properties from other interfaces using the extends keyword:

interface Entity {
  id: string;
  createdAt: Date;
}

interface UserEntity extends Entity {
  name: string;
  email: string;
}

const user: UserEntity = {
  id: "usr_100",
  createdAt: new Date(),
  name: "John Doe",
  email: "[email protected]",
};

Summary & Next Steps
#

In this episode:

  • We learned the 3 ways to define object types: inline, type, and interface.
  • We unpacked Structural Typing (matching shapes rather than nominal type names).
  • We analyzed Excess Property Checks on direct inline object literals.
  • We compared key?: string (omittable) vs key: string | undefined (required key).
  • We introduced readonly modifiers, index signatures, and interface extends.

In Episode 5: Arrays and Tuples, we will explore ordered data collections, typed arrays, fixed-length tuples, and readonly arrays!

typescript - This article is part of a series.
Part 4: This Article