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 Promiseand theresolvecallback - Attaching
.thenhandlers to process resolved values - Chaining promises to sequence dependent asynchronous operations
- Handling errors with
.catchand the second argument to.then - Understanding the difference between catching errors on the current promise vs. the chain
- Propagating rejections with
throwandPromise.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
.thenchains toasync/awaitfor linear, readable flow - Understanding how
asyncimmediately changes a function's return type toPromise - Handling errors with
try/catch/finallyinstead of.catch - Adding explicit
Promise<Type>return type annotations to async functions - Using top-level
awaitin 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
exportat declaration or as export specifiers - Importing named exports and aliasing with
as - Using
import * asto grab everything from a module - Default exports vs. named exports and why named exports are preferred
- Type-only imports with
import typefor 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
keyofto extract a union of property names from an object type - Pulling runtime values into the type world with
typeof - Combining
keyof typeofto 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 constwith 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
Partialand required withRequired - Selecting subsets with
Pickand removing properties withOmit - Creating dynamic object types with
Recordand immutable ones withReadonly - Filtering union types with
Exclude,Extract, andNonNullable - Extracting function signatures with
ReturnType,Parameters, andAwaited - 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
inferkeyword to extract types from within other types - Unwrapping promise types and array element types with
infer - Recreating built-in utilities like
ReturnTypeto understand how they work
