Software Development

Why TypeScript Will Never Add Strict Omit

The Omit type from TypeScript is counter-intuitive. Let me explain why.

Take this Example type with properties x, y:

type Example = {
  x: 1
  y: 2
}

type OmitExample = Omit<Example, "x">
// type OmitExample = {
//     y: 2;
// }

type OmitExample1 = Omit<Example, "xyz">
// type OmitExample1 = {
//     x: 1;
//     y: 2;
// }

In OmitExample, I construct a new Example type without the x, and the result is as you would expect.

In OmitExample1, I remove the xyz property. But hang on, this doesn't exist on Example, so shouldn't I get a type error?

Well no you shouldn't, and that is because Omit is loose by default rather than strict.

Now before I dive into why this is true, let's first figure out how you can make Omit strict.

How to make Omit strict

Strict Omit is not built into Typescript, so we need to create a custom type StrictOmit to handle this:

type StrictOmit<T, K extends keyof T> = Omit<T, K>

This type is almost identical to Omit but ensures the second argument - the key we want to omit - is present in the first argument.

If we apply it to the example above, we now get a type error as expected!

type StrictOmit<T, K extends keyof T> = Omit<T, K>

type OmitExampleStrict = StrictOmit<Example, "x">
// type OmitExampleStrict = {
//     y: 2;
// }

// TYPE ERROR HERE!!! "xyz" is not a key in Example.
type OmitExampleStrict1 = StrictOmit<Example, "xyz">
// type OmitExampleStrict1 = {
//     x: 1;
//     y: 2;
// }

Why Omit is loose by default

The main reason is pretty simple.

It's easy to construct a strict Omit very quickly as we've seen above.

Additionally, loose Omit actually has some valid use cases, one of which is property spreading.

Property spreading allows you to overwrite properties of one type with another.

Here's how I would construct the Spread type to make this work:

type Spread<T, K> = K & Omit<T, keyof K>
  • We take two arguments, the former is the type to be overwritten and the latter contains the properties to overwrite from the first type.
  • We intersect the second type with the first type excluding properties from the second type that exists in the first type.

Pretty cool right? Here is an example that overwrites the y property in an object from a literal type 2 to string:

type SpreadExample = Spread<{ x: 1; y: 2 }, { y: string }>
// type SpreadExample = {
//     y: string;
// } & Omit<{
//     x: 1;
//     y: 2;
// }, "y">

In other words, the final type will look like this:

type AfterSpread = {
  x: 1
  y: string
}

Conclusion

So this is the reason why Typescript will never include strict omit as a native type.

I hope you found this explanation useful, and I hope the implementation of StrictOmit will come in handy for your Typescript projects.


If you enjoyed this article, please make sure to Subscribe, Clap, Comment and Connect with me today! 🌐
1