Skip to Content
Course content

245: What's New Across Recent TypeScript Versions

Click on the "Edit" button in the top corner of the screen to edit your slide content.

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 satisfies when 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 const inside 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 passing string[]. To stop this, we've spent years slapping as const onto every single argument.

The const type parameter (introduced in 5.0) lets the function author decide that the input should be treated as a literal. It basically forces as const on 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 const just 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: true in tsconfig.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 : AppConfig with the satisfies operator.
  • Ensure that the config.endpoints.api access remains type-safe as a specific string literal, not just a general string.
  • Add a new property to the AppConfig type called timeout (a number) and update the config object to include it, ensuring the satisfies operator 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'));
Rating
0 0

There are no comments for now.

to be the first to leave a comment.