TypeScript 4.9 beta is introducing a new operator, the “satisfies” operator. The short version is that this operator lets you ensure a variable matches a type, while keeping the most specific version of that type.

You can check out the discussion on the feature here, but in this article, I’ll talk you through how to use it, and why it exists.

Problem + Solution

Let’s take an example imagining we’re defining some CSS values. You might end up running into some similar code if you’re using TypeScript with React. Here’s the code:

const Container = {
    width: 10,
    height: '50%'
}

So we have some container, and if you’re familiar with CSS, you’ll be used to mixing together different units, such as pixels, percent, em, etc.

So how do we describe this in a type? You might write something like this:

type Length = number | string;
type Container = Record<string, Length>;

const container: Container = {
    width: 10,
    length: '50%',
};

An easy solution; now TypeScript knows our container variable is an object, with width and length as keys, and numbers or strings as values.

This looks fine, but we’ve just lost a level of specificity in our object.

We know width is a number, we’ve just defined it as one, but now TypeScript doesn’t:

const container: Container = {
    width: 10,
    length: '50%',
};

container.width++; //❎ TS2356: An arithmetic operand must be of type 'any', 'number', 'bigint' or an enum type

This is where the satisfies operator comes in:

const container = {
    width: 10,
    length: '50%'
} satisfies Container;

The satisfies operator tells TypeScript our variable fits in the shape of our Container type, without changing it to its less specific shape.

We can keep the specific shape and use it however we need, but still use it anywhere we expect a container:

container.length.charAt(0);
container.width++;

function iJustWantContainers(c: Container) {}

iJustWantContainers(container);

// ^All of these work!

Checking Keys with “satisfies”

In our previous example we used these types:

type Length = number | string;
type Container = Record<string, Length>;

We’ve allowed anything as a key, and this might work fine for our imaginary use case, but what if we wanted to check that our type has all the keys of a type?

This is really easy to implement with satisfies:

Let’s introduce a new type, for the properties we want to make sure we have, and use this in our Container type:

type Dimension = 'width' | 'length' | 'depth';
type Length = number | string;
type Container = Record<Dimension, Length>;

I’ve added in a new property “depth”, and immediately we get an error:

const container = {
    width: 10,
    length: '50%',
} satisfies Container;
//Type '{ width: number; length: string; }' does not satisfy the expected type 'Container'.
 Property 'depth' is missing in type '{ width: number; length: string; }' but required in type 'Container'.

“satisfies” gives us an easy way of checking we’ve used all of these keys. To get rid of this error, we just need to add the missing key:


const container = {
    width: 10,
    length: '50%',
    depth: 70
} satisfies Container;

You might run into a use case where we don’t need our variable to use all of the keys, but we want to make sure it only uses those keys. You can implement this using the utility type Partial:

const container = {
    width: 10,
    length: '50%',
    beef: 
//Object literal may only specify known properties, and 'beef' does not exist in type 'Partial<Container>'.
} satisfies Partial<Container>;

I’ve kept the same type as before, including “depth”. Wrapping container in a partial means our container doesn’t have to include depth, but it will throw an error when we try to include anything else.

Conclusion

Thanks for reading this article on a cool new feature in TypeScript. Hopefully, I’ve helped you to understand what it’s for, and why it’s a welcome addition.

You can do all of this without satisfies, but the difference this keyword makes vs casting it with an “as”, is “satisfies” doesn’t change the original type of our variable. TypeScript still knows width is a number, and length is a string. If we cast it using “as”, we’d lose that information.

If you liked this article, or if I’ve helped you out, feel free to leave a comment below!

Avatar photo
👋 Hey, I'm Omari Thompson-Edwards
Hey, I'm Omari! I'm a full-stack developer from the UK. I'm currently looking for graduate and freelance software engineering roles, so if you liked this article, reach out on Twitter at @marile0n

💬 Leave a comment

Your email address will not be published. Required fields are marked *

We will never share your email with anyone else.