Workshop under development — you're viewing a draft version.
Pro Workshop

Type Safety

Type Safety

In TypeScript, every variable, every function parameter, every return value carries a type. When those types are wrong, you get bugs that surface at the worst possible moment. Runtime crashes, silent data corruption, and hours spent debugging issues that the compiler could have caught before you ever hit save.

The problem is that you haven't built the mental model for how its type system actually works. You annotate a few variables, sprinkle in some any when things get frustrating, and move on, never realizing you've turned off the very safety net that makes TypeScript worth using.

The Type Safety Workshop takes you deep into TypeScript's type system. You'll learn how to model your data precisely with type aliases, union types, and generics, then use that precision to catch entire categories of bugs at compile time instead of in production.

In this workshop, you'll work through real patterns like discriminated unions for API responses, type guards for runtime narrowing, and as const for deriving types from your source code, the same techniques used in production TypeScript codebases every day.

Type Aliases

Every time you duplicate a type annotation across multiple variables or functions, you're creating a maintenance problem. Change the shape in one place, forget the other, and you've got a type mismatch that might not surface for weeks. Type aliases let you define a type once and reference it everywhere.

In this section, you'll create reusable types and learn how to compose them from smaller building blocks:

  • Creating type aliases for objects with type User = { id: string; name: string }
  • Using typeof to derive types from existing runtime values
  • Composing types from primitive aliases like ID, Timestamp, and Email to communicate intent
  • Using Record to type objects with dynamic string keys
  • Choosing between explicit type definitions and inference with typeof

Union Types

Real application data is rarely just one thing. An API response might be a success object or an error. A user ID might be a string or a number. Without union types, you're forced to use any or write separate functions for every possible shape-- neither of which scales.

This section covers one of TypeScript's most powerful features and the patterns that make it practical:

  • Defining types that accept multiple shapes with the | operator
  • Narrowing unions with typeof and instanceof checks
  • Using the in operator to distinguish between object shapes
  • Writing custom type guard functions with the value is Type syntax
  • Building discriminated unions with a shared status or type property
  • Exhaustive checking with switch statements and the never type

Literal Types

TypeScript infers const x = "hello" as the literal type "hello", but let y = "hello" becomes just string. That distinction matters more than you'd think-- especially when you want your types to reflect the exact values in your code rather than broad categories.

Here you'll learn how to preserve and leverage literal types in your programs:

  • Using as const to preserve literal types on objects and arrays
  • Deriving union types from object keys with keyof typeof
  • Extracting value types from constant objects for precise unions
  • Building a single source of truth where types flow from runtime values
  • Practical patterns like typed route maps and discriminated return types

Intersection Types

Union types let a value be one of several shapes. Intersection types go the other direction-- combining multiple types into one that has all their properties. It's the type-level equivalent of spreading objects together.

This section teaches you how to compose types from reusable pieces:

  • Combining types with the & operator to build composite shapes
  • Creating reusable building blocks like WithID, WithTimestamps, and WithAuthor
  • Assembling entity types (User, Post, Comment) from shared base types
  • Understanding how intersection differs from union in practice

Any vs Unknown

When TypeScript gets in your way, it's tempting to reach for any and move on. The problem is that any doesn't just silence one error-- it turns off type checking for everything that value touches. It's an escape hatch that defeats the purpose of using TypeScript in the first place.

This section shows you the safer alternative and when each escape hatch is appropriate:

  • Understanding why any disables all type checking on a value
  • Using unknown as a type-safe alternative that requires narrowing before use
  • Narrowing unknown values with typeof checks and error throwing
  • Processing values of different types through runtime checks
  • When you'll encounter any in library code and why it exists for backward compatibility

Generics

Without generics, you'd need a separate function for every type you want to support-- firstNumber, firstString, firstUser-- or you'd give up and use any. Generics let you write a single function that works with any type while preserving full type safety through the entire call chain.

This section takes you from basic generic syntax through real-world patterns with constraints:

  • Writing generic functions with type parameters (<Value>)
  • Letting TypeScript infer generic types from function arguments
  • Creating generic types for reusable patterns like loading states
  • Constraining generics with extends to require specific shapes
  • Building a type-safe getProperty function using keyof constraints
  • Merging objects with fully typed intersection return values

Interfaces

Interfaces look almost identical to type aliases-- and for most purposes, they behave the same way. So why do they exist? Historical reasons, mostly. But interfaces have one unique capability that types can't replicate: declaration merging.

Here you'll learn when interfaces matter and when to stick with types:

  • Defining object shapes with interface syntax
  • Extending interfaces to build up from base types
  • Understanding declaration merging and how interfaces combine across modules
  • Augmenting global interfaces for plugin systems and library configuration
  • Why types are preferred for day-to-day code and when interfaces earn their place

Enums

Enums give you a way to define a set of named constants-- OrderStatus.Pending, OrderStatus.Active, OrderStatus.Completed. They're one of the few TypeScript features that generate actual runtime code, which makes them unique in the type system and controversial in the community.

In this section, you'll learn how enums work and why union types are often the better choice:

  • Creating string enums for order statuses and log levels
  • Using enums in switch statements and type-safe functions
  • Understanding the runtime code that enums generate after compilation
  • Migrating from enums to union types for simpler, cleaner code
  • Comparing the developer experience of enums vs. unions for autocomplete and type safety