Lab 03: Interfaces & Utility Types

Objective

Use advanced interface patterns, built-in utility types (Partial, Required, Pick, Omit, Record, Readonly), mapped types, and conditional types to express complex data shapes.

Background

TypeScript's type system goes far beyond basic annotations. Utility types let you transform existing types — create a "partial" version of an interface for updates, pick only the fields you need, or make everything readonly. Mapped types and conditional types give you a "type-level programming" capability that catches entire classes of bugs at compile time.

Time

30 minutes

Prerequisites

  • Lab 01, Lab 02

Tools

  • Docker image: zchencow/innozverse-ts:latest


Lab Instructions

Step 1: Interface Patterns

💡 Interface declaration merging lets you add properties to existing interfaces — including built-in ones like Window, Array, or HTMLElement. This is how DefinitelyTyped (the @types packages) augment third-party libraries. It's a double-edged sword: powerful for library authors, dangerous if overused.

📸 Verified Output:


Step 2: Built-in Utility Types

💡 Omit<User, 'password'> is the most important utility type for APIs — create a type without sensitive fields. Pick and Omit are complementary: use Pick when you want a few fields, Omit when you want all-but-a-few. Both produce new types without modifying the original.

📸 Verified Output:


Step 3: Mapped Types

💡 Mapped types + key remapping (as \get${Capitalize<...>}`) lets you generate new types from old ones — adding prefixes, converting names, filtering by value type. This is how TypeScript's own Partial, Readonly, and Required` utility types are implemented internally.

📸 Verified Output:


Step 4: Conditional Types

💡 infer keyword lets you extract types from within other types. T extends Promise<infer R> means "if T is a Promise of something, call that something R." This is how ReturnType<F>, Awaited<T>, and Parameters<F> are implemented in TypeScript's standard library.

📸 Verified Output:


Step 5: Template Literal Types

💡 Template literal types generate string literal unions from combinations of other literals. \on${Capitalize}`produces 4 types from 4 events.`${Method} ${Path}`` produces 12 route types from 3 methods × 4 paths. This enables typed route definitions, event maps, and CSS-in-JS systems.

📸 Verified Output:


Step 6: Discriminated Unions

💡 Discriminated unions + exhaustiveness checking is TypeScript's killer pattern for state machines. If you add a new variant (e.g., | { kind: "ellipse"; ... }), TypeScript immediately errors at every switch statement that doesn't handle it. This is impossible in JavaScript — you'd only find the bug at runtime.

📸 Verified Output:


Step 7: Recursive Types

📸 Verified Output:


Step 8: Complete — Type-Safe API Client

💡 ApiParams<M> and ApiResponse<M> are indexed access types — they look up the params and response fields from the Endpoints interface using the method string as a key. This ties the request parameters and response type to the specific endpoint, making client.request() fully type-safe.

📸 Verified Output:


Summary

TypeScript's type system is a full programming language at the type level. You've used built-in utility types, mapped types with key remapping, conditional types with infer, template literal types, discriminated unions with exhaustiveness checking, recursive types, and a fully type-safe API client. These patterns are used in every professional TypeScript codebase.

Further Reading

Last updated