03open 25 min

Data and state modeling: interfaces, type aliases, unions, and discriminated unions

Move from Python's dynamic data shaping into explicit TypeScript models for objects, variants, and state transitions.

by the end of this lesson you can

  • Separates shape definition from runtime value clearly
  • Uses interface or type alias intentionally
  • Makes optional versus required fields explicit instead of implied

Overview

Python developers are used to dicts, classes, dataclasses, and conventions that stay flexible until runtime. TypeScript rewards you for deciding earlier what shapes exist, which variants are legal, and how state changes should be represented in the type system.

In Python, you often

model evolving data with dicts, dataclasses, or informal conventions that callers are expected to respect.

In TypeScript, the common pattern is

to make object shapes and variant states explicit using interfaces, type aliases, unions, and discriminated unions.

why this difference matters

This is where TypeScript becomes more than annotated JavaScript. Good data modeling catches ambiguity early and replaces many I hope this dict has the right keys assumptions with readable contracts.

Python

response = {"status": "ok", "data": user}

TypeScript

type ApiResponse =
  | { status: "ok"; data: User }
  | { status: "error"; message: string };

Deeper comparison

Python version

event = {"kind": "signup", "email": "ana@example.com"}

TypeScript version

type Event =
  | { kind: "signup"; email: string }
  | { kind: "purchase"; orderId: string };

Reflect

Why can a discriminated union be a better model for dynamic application data than a Python-style flexible dictionary?

what a strong answer notices

A strong answer mentions explicit legal variants, better narrowing in callers, and fewer hidden assumptions about which keys exist together.

Rewrite

Rewrite this Python-style config shape into TypeScript so optional and required fields are modeled explicitly.

Rewrite this Python

config = {"region": "us", "timeout": 30, "debug": False}

what good looks like

  • Separates shape definition from runtime value clearly
  • Uses interface or type alias intentionally
  • Makes optional versus required fields explicit instead of implied

Practice

Design TypeScript types for an API client that can return loading, success, or error states without leaving the caller to guess which fields exist.

success criteria

  • Uses a discriminated union or equivalent explicit variant model
  • Keeps each state shape easy to understand
  • Shows how the caller would branch safely on the result

Common mistakes

  • Treating interfaces as mere replacements for Python dict annotations instead of as contract tools.
  • Collapsing multiple states into one broad object type full of optional fields.
  • Reaching for classes when a union of object shapes would communicate the state model more clearly.

takeaways

  • This is where TypeScript becomes more than annotated JavaScript. Good data modeling catches ambiguity early and replaces many I hope this dict has the right keys assumptions with readable contracts.
  • A strong answer mentions explicit legal variants, better narrowing in callers, and fewer hidden assumptions about which keys exist together.
  • Uses a discriminated union or equivalent explicit variant model