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

Advanced TypeScript

You now know the basics of TypeScript. You can annotate variables, define types, and write generic functions. But the moment you need to fetch data from an API, organize code across multiple files, or build a utility type that transforms other types, the beginner patterns stop being enough.

The gap between "I can use TypeScript" and "I can use TypeScript well" comes down to the advanced features that power every serious codebase:

  • Promises and async/await for handling asynchronous operations.
  • Modules for organizing code at scale.
  • Utility types and conditional types for writing the kind of precise, flexible types that make libraries and frameworks feel magical to use.

In this workshop, you'll work through the advanced TypeScript features that separate everyday usage from real fluency. You'll build a deep understanding of promises and async/await, learn to structure code with modules, and master the type-level programming that powers TypeScript's most useful patterns.

Every section goes beyond syntax. You'll trace how promises chain and resolve, debug asynchronous code with real tools, and build your own utility types from scratch so the built-in ones stop feeling like magic.

Promises

JavaScript runs on a single thread. When you make a network request, read a file, or wait for user input, you can't just stop the program and wait. Promises are how JavaScript handles this-- they represent a value that doesn't exist yet but will at some point in the future.

This section builds your mental model for how promises actually work under the hood:

  • Creating promises with new Promise and the resolve callback
  • Attaching .then handlers to process resolved values
  • Chaining promises to sequence dependent asynchronous operations
  • Handling errors with .catch and the second argument to .then
  • Understanding the difference between catching errors on the current promise vs. the chain
  • Propagating rejections with throw and Promise.reject

Async/Await

Promises solve the problem of asynchronous code, but chaining .then calls creates its own kind of complexity. Async/await is syntactic sugar that lets you write asynchronous code that reads like synchronous code-- no callbacks, no indentation pyramids, just await and move on.

Here you'll refactor promise chains into async/await and learn how error handling maps between the two:

  • Converting .then chains to async/await for linear, readable flow
  • Understanding how async immediately changes a function's return type to Promise
  • Handling errors with try/catch/finally instead of .catch
  • Adding explicit Promise<Type> return type annotations to async functions
  • Using top-level await in modules
  • Why you can't type the rejection value of a promise

Modules

Through this entire workshop series, you've been using export at the bottom of every file without formally learning what it does. Modules are how JavaScript organizes code across files-- exporting values from one file and importing them in another.

This section covers the module system you'll use in every TypeScript project:

  • Exporting values with export at declaration or as export specifiers
  • Importing named exports and aliasing with as
  • Using import * as to grab everything from a module
  • Default exports vs. named exports and why named exports are preferred
  • Type-only imports with import type for cleaner compiled output
  • Mixing type and value imports in a single statement

Type Operators

You've been using keyof, typeof, and index access types throughout the workshop series because it's hard to get far in TypeScript without them. Now it's time to formalize that knowledge and go deeper into what these operators can do when combined.

In this section, you'll master the operators that bridge runtime values and the type system:

  • Using keyof to extract a union of property names from an object type
  • Pulling runtime values into the type world with typeof
  • Combining keyof typeof to derive types from actual objects
  • Accessing nested types with index access syntax (User["address"]["city"])
  • Extracting element types from arrays with [number] index access
  • Using as const with type operators for precise literal unions

Utility Types

TypeScript ships with a library of generic types that transform other types. Need to make all properties optional? Partial. Want only certain fields? Pick. These built-in utilities handle the type transformations you'd otherwise write by hand, and knowing them saves you from reinventing the wheel.

This section teaches you the most useful utility types and how to build your own:

  • Making properties optional with Partial and required with Required
  • Selecting subsets with Pick and removing properties with Omit
  • Creating dynamic object types with Record and immutable ones with Readonly
  • Filtering union types with Exclude, Extract, and NonNullable
  • Extracting function signatures with ReturnType, Parameters, and Awaited
  • Building custom mapped types that replicate and extend the built-in utilities

Conditional Types

Conditional types are TypeScript's version of an if statement at the type level. They use the extends keyword to check whether a type matches a shape, then resolve to one type or another based on the result. Most developers never write them directly, but they power the utility types and library APIs you use every day.

This section takes you into the most advanced corner of TypeScript's type system:

  • Writing conditional types with the extends ? : ternary syntax
  • Checking whether a type is a string, array, or function at the type level
  • Using the infer keyword to extract types from within other types
  • Unwrapping promise types and array element types with infer
  • Recreating built-in utilities like ReturnType to understand how they work