-
Section 1: Getting Started
-
Section 2: Basic Types
-
Section 3: Functions and Objects
-
Section 4: Advanced Types
-
Section 5: Object-Oriented TypeScript
-
Section 6: Working with Modules
-
Section 7: Tooling and Practice
-
Section 8: Type-Level Programming
-
Section 9: TypeScript with Backends
-
Section 10: Testing Typed Code
-
Section 11: Data Validation with Types
-
Section 12: Practical Projects
-
Section 13: Compiler Internals
-
Section 14: Configuration Deep Dive
-
Section 15: Enums, Symbols, and Special Types
-
Section 16: Working with Async Code
-
Section 17: TypeScript and the DOM
-
Section 18: Advanced Generics Practice
-
Section 19: Working with Third-Party Types
-
Section 20: Monorepo and Large-Scale Practices
-
Section 21: Common Pitfalls and Best Practices
-
Section 22: Interview Practice
-
Section 23: Handbook: Narrowing In Depth
-
Section 24: Handbook: Object Types In Depth
-
Section 25: Handbook: Classes In Depth
-
Section 26: Handbook: Modules In Depth
-
Section 27: Handbook: Declaration Files In Depth
-
Section 28: JSX and Namespaces
-
Section 29: Compiler Configuration Reference
-
Section 30: More Practice Exercises
-
Section 31: Handbook: Everyday Types Deep Dive
-
Section 32: Utility Types Full Reference
-
Section 33: Decorators Reference
-
Section 34: Mixins and Advanced OOP Patterns
-
Section 35: Iterators and Generators Typing
-
Section 36: More Type-Level Programming Practice
-
Section 37: TypeScript Ecosystem Tools
-
Section 38: TypeScript with Testing Frameworks
-
Section 39: TypeScript for Library Authors
-
Section 40: More Interview Practice
-
Section 41: Handbook: Functions In Depth
-
Section 42: Handbook: Type Manipulation Deep Dive
-
Section 43: More Real-World Patterns
-
Section 44: TypeScript Release Notes Highlights
-
Section 45: Final Practice and Review
245: What's New Across Recent TypeScript Versions
TypeScript moves fast. If you've been away for a few versions or just haven't had a reason to dive into the changelogs, you've probably noticed some new keywords and symbols popping up in the wild. Instead of boring you with a version-by-version list, let's talk about the stuff that actually changes how you'll write your code on a Tuesday afternoon.
What's the deal with the satisfies operator? How is it different from a regular type annotation?
This is probably the most useful addition in recent memory. For a long time, we had a problem: if you annotated a variable with a type, TypeScript would "widen" the type to that interface, and you'd lose the specific literal values. But if you didn't annotate it, you risked typos in your object keys.
I used to handle this with a hacky type assertion or by just hoping for the best. Now, satisfies lets you validate that an object matches a type without changing the inferred type of that object.
type Colors = 'red' | 'green' | 'blue'; type Palette = Record<string, Colors>; // This is the old way. 'red' is now just a 'Colors' type. const palette1: Palette = { primary: 'red', secondary: 'green' }; // Error: TypeScript doesn't know for sure if 'primary' exists on Palette // in a way that allows string methods, because it's just a general Record. // palette1.primary.toUpperCase(); // This is the 'satisfies' way. const palette2 = { primary: 'red', secondary: 'green' } satisfies Palette; // This works! TypeScript knows palette2.primary is exactly 'red'. palette2.primary.toUpperCase();In short: use
satisfieswhen you want to ensure a value is "correct" according to a type, but you still want the compiler to remember the specific values you actually typed in.I keep seeing
constinside the angle brackets of a generic. Why would I actually use that?Usually, when you pass an array or a tuple into a generic function, TypeScript widens the elements. If you pass
['a', 'b'], it thinks you're passingstring[]. To stop this, we've spent years slappingas constonto every single argument.The
consttype parameter (introduced in 5.0) lets the function author decide that the input should be treated as a literal. It basically forcesas conston the caller's behalf.// Without the 'const' modifier function createRoute<T>(paths: T[]) { return paths; } const routes1 = createRoute(['/home', '/about']); // routes1 is string[] // With the 'const' modifier function createRouteConst<const T>(paths: T[]) { return paths; } const routes2 = createRouteConst(['/home', '/about']); // routes2 is readonly ["/home", "/about"]I love this because it cleans up the call site. Your API users don't have to remember to add
as constjust to get the type safety they expect from a specialized function.Did decorators finally change? Do I need to stop using the experimental ones?
Yes, they did. For years, we used
experimentalDecorators: trueintsconfig.json, which was basically a "best guess" at what the JavaScript standard would become. TypeScript 5.0 finally implemented the official ECMAScript decorators.The biggest shift is that the new decorators are more powerful and don't rely on the same "magic" as the old ones. They have a very specific context object that tells you exactly what you're decorating (a class, a method, a field) and gives you a way to replace the original logic.
If you're starting a new project, I'd suggest moving toward the standard. However, if you're deep in a NestJS or Angular project, keep the experimental flag on. Those frameworks were built on the old spec, and switching them over isn't as simple as flipping a boolean—it would require a massive rewrite of their internals.
📋 Practical Task
Exercise: Refactoring a Configuration Validator with satisfies
You have a configuration object for a notification system. Currently, it's typed using a standard type annotation, which is causing the developer to lose autocomplete for the specific keys they defined, forcing them to use type casting throughout the app.
Your Task:
- Take the following code and replace the type annotation
: AppConfigwith thesatisfiesoperator. - Ensure that the
config.endpoints.apiaccess remains type-safe as a specific string literal, not just a generalstring. - Add a new property to the
AppConfigtype calledtimeout(a number) and update theconfigobject to include it, ensuring thesatisfiesoperator catches any type mismatches.
type AppConfig = {
endpoints: {
api: string;
socket: string;
};
retryAttempts: number;
};
const config: AppConfig = {
endpoints: {
api: 'https://api.myapp.com',
socket: 'wss://socket.myapp.com',
},
retryAttempts: 3,
};
// Goal: Make this line work without a type cast
console.log(config.endpoints.api.startsWith('https'));
There are no comments for now.