Want a faster signal before interviews? Take the Interview Ready Check →

DAY 19 OF 30

Type assertions and satisfies

Yesterday, in one sentence

What is the never type in TypeScript?

Interview question

How would you make TypeScript report an error when a new object type is added to a discriminated union and you have not handled it?

A type assertion tells TypeScript to treat a value as a particular type. You write it with the as keyword.

const power = value as string;

A type assertion does not convert anything and it does not check anything. It only changes the type TypeScript uses for that value.

You can use a type assertion when TypeScript's type for a value does not match what you know the value to be. This usually happens with values coming back from code you did not write. For example, JSON.parse returns any, because it is defined outside your code and has no way to know what is in the string you passed it. You might know that this particular string will parse into an object with the type { name: string }.

const stored = JSON.parse(raw) as { name: string };

stored.name.toUpperCase();
// allowed, because you asserted it was the object type { name: string }

If the stored data does not match, the TypeScript compiler will not catch it, and the error appears at runtimeRuntime instead.

TypeScript will refuse an assertion between two types with nothing in common.

const rank = 41 as string;
// ❌ Conversion of type 'number' to type 'string' may be a mistake

satisfies

satisfies checks a value against a type without changing it. Where as overrides the type and checks nothing, satisfies checks and keeps what was inferred.

The problem it solves shows up when the declared type is wider than the value you wrote. For example, the type declares string | number, and later you want to use a number method on it.

type Hero = {
  level: string | number;
};

const annotated: Hero = {
  level: 9,
};
// level: string | number

annotated.level.toFixed();
// ❌ Property 'toFixed' does not exist on type 'string | number'

You wrote a number, but the annotation replaced that with string | number, so TypeScript will not let you use it as a number. Removing the annotation fixes that, but then nothing checks the object against Hero at all.

Putting satisfies Hero after the object checks it against Hero, then leaves the inferred type in place. So level stays a number rather than becoming string | number.

type Hero = {
  level: string | number;
};

const hero = {
  level: 9,
} satisfies Hero;
// level: number

hero.level.toFixed(); // ok, level is a number

The value is still checked, so a bad one fails.

const broken = {
  level: true,
} satisfies Hero;
// ❌ Type 'boolean' is not assignable to type 'string | number'

Three ways to write the same object, and what each one gives you:

  • type annotation checks the value, but widens it to the declared type
  • type inference keeps the inferred type, but checks nothing
  • satisfies checks the value and keeps the inferred type

Check yourself

What is the type of hero.level here?
type Hero = {
  level: string | number
}

const hero = {
  level: 9
} satisfies Hero
What is a type assertion in TypeScript, and why would you use one?