Search code examples
typescript

What are the differences Between TypeScript’s satisfies operator and type assertions?


I've recently come across the satisfies operator in TypeScript and I'm trying to understand how it differs from regular type assertions. Both seem to provide a way to work with types, but I'm not clear on when to use one over the other.

  • How does the satisfies operator enhance type safety compared to type assertions?
  • What kind of errors can be caught using satisfies that might be missed with type assertions?
  • When should I prefer using satisfies over type assertions and vice versa?
// Satisfies
interface User1 {
    username: string;
    passcode: number;
}

const user1 = {
    username: "joe",
    passcode: 2076,
    email: "[email protected]"
} satisfies User1;

// Type Assertion
interface User2 {
    username: string;
    passcode: number;
}

const user2 = {
    username: "doe",
    passcode: 3000,
    email: "[email protected]"
} as Person;

Solution

  • A type assertion is a way to disable typescript. as Person tells typescript "trust me, I know more than you do, it's a Person". Typescript will for the most part take your word on this, and so if you make a mistake, it will not point it out (it will point out very egregious mistakes, which then requires you to do as unknown as Person to confirm you're sure).

    satisfies Person is fully type checked. It tells typescript "infer the type based on the code you see, but give me an error if the resulting type is incompatible with Person".

    When should I prefer using satisfies over type assertions and vice versa?

    Type assertions are for when you know things that typescript does not. For example, maybe you're doing something like document.querySelector('#foo') which gets an element from the page. Typescript has no idea what that element will be, so it gives the broadest type: Element | null. But you know for a fact that there's a div element on the page with that id. You can use a type assertion to tell typescript "trust me, it's an HTMLDivElement".

    const element = document.querySelector('#foo') as HTMLDivElement
    

    Satisfies should be used when you need the type to be based on the exact code, but you want typescript to alert you to mistakes you made. For example, maybe you have a type that has a union for one of its properties:

    type Example {
      id: string | null
    }
    

    If you used the normal way of declaring a type, you'd have to check whether the id is null, even if it seems obvious that you just created it to be a string one line earlier.

    const foo: Example = {
      id: 'hello'
    }
    console.log(foo.id.toUpperCase()); // Error because the id might be null
    

    If you use satisfies, the type is inferred from the code, and so typescript knows the id is exactly a string:

    const foo = {
      id: 'hello'
    } satisfies Example
    console.log(foo.id.toUpperCase()); // Allowed
    

    And at the same time, typescript will alert you to issues

    const foo = {
      ID: 'hello' // Error, because 'id' is in uppercase
    } satisfies Example
    const bar = {
      id: 3 // Error, because 'id' is not a string or null
    } satisfies Example