Announcing: A Free Book, A New Course, A Huge Price Cut...
It's a massive ship day. We're launching a free TypeScript book, new course, giveaway, price cut, and sale.
The empty object type - {}
- doesn't behave how you expect in TypeScript.
Instead of representing an empty object, it represents any value except null
and undefined
.
This is because TypeScript's type system is structural, not nominal. Everything except null
and undefined
is an object, so everything can be assigned to an empty object.
If you want to represent an empty object, use Record<string, never>
instead.
The empty object type in TypeScript doesn't really behave as you expect. It doesn't represent "any object". Instead, it represents any value that isn't null
or undefined
.
Try experimenting with it in the playground below:
Here, we are basically typing example1
as an empty object, yet we can pass a string to it. We can pass a number to it. We can pass a boolean to it and any object to it.
The only things we can't pass to it are null
or undefined
:
const example5 : {} = null;Type 'null' is not assignable to type '{}'.2322Type 'null' is not assignable to type '{}'.const example6 : {} = undefined ;Type 'undefined' is not assignable to type '{}'.2322Type 'undefined' is not assignable to type '{}'.
If you're using ESLint, you might even hit an error:
Don't use
{}
as a type.{}
actually means "any non-nullish value".
This article explains exactly why this is good advice.
Object
TypeThis also happens with the Object
type, which I think is just an alias over the top of {}
:
const obj1 : Object = "str";
const obj2 : Object = null;Type 'null' is not assignable to type 'Object'.2322Type 'null' is not assignable to type 'Object'.
So this behaves in exactly the same way. So you shouldn't be using this Object
type either.
If you do want to represent a kind of empty object, then we can use this Record<PropertyKey, never>
.
type EmptyObj = Record <PropertyKey , never>;
const emptyObj1 : EmptyObj = {};
const emptyObj2 : EmptyObj = {
foo : "whatever",Type 'string' is not assignable to type 'never'.2322Type 'string' is not assignable to type 'never'.};
const emptyObj3 : EmptyObj = "str";Type 'string' is not assignable to type 'EmptyObj'.2322Type 'string' is not assignable to type 'EmptyObj'.const emptyObj4 : EmptyObj = 123;Type 'number' is not assignable to type 'EmptyObj'.2322Type 'number' is not assignable to type 'EmptyObj'.
What this does is it means that you can pass an empty object. It's going to stop you from passing anything with a property on it. And it's also going to stop you from passing primitives, or null or undefined.
The only time this might be useful is if you want a really wide constraint on a function. Let's say you want to make sure that the thing that you're passing that function isn't null
or undefined
:
const myFunc = (constraint : {}) => {};
myFunc ("str");
myFunc (123);
myFunc (true);
But even then, you're not going to be able to access any properties on constraint
:
const myFunc = (constraint : {}) => {
constraint .foo ;Property 'foo' does not exist on type '{}'.2339Property 'foo' does not exist on type '{}'.};
You'll hit this error:
Property 'foo' does not exist on type ''.
This is happening because we're trying to access a property on an empty object. So it's not terribly useful.
The only possible place it could be useful - or at least the one I want to cover here - is as a constraint in a generic function.
const myGenericFunc = <T extends {}>(t : T ) => {
return t ;
};
const result1 = myGenericFunc ("str");
const result2 = myGenericFunc (123);
const result3 = myGenericFunc (true);
Inside myGenericFunc
, we want to make sure that we can't pass null
or undefined
into the generic function. If we hover over myGenericFunc
here, you can see that it's capturing str
and returning it in the results here.
But it fails when we try to pass in null or undefined.
const result4 = myGenericFunc (null );Argument of type 'null' is not assignable to parameter of type '{}'.2345Argument of type 'null' is not assignable to parameter of type '{}'.const result5 = myGenericFunc (undefined );Argument of type 'undefined' is not assignable to parameter of type '{}'.2345Argument of type 'undefined' is not assignable to parameter of type '{}'.
So, you probably shouldn't be using the {}
type really anywhere. And you'll have likely come to this article because you found a linting rule that's preventing you from using it. That is why it doesn't represent what you think, but it's occasionally useful for constraining generics.
Got any more questions? Found any more use cases? Let me know:
Share this article with your friends
It's a massive ship day. We're launching a free TypeScript book, new course, giveaway, price cut, and sale.
Learn why the order you specify object properties in TypeScript matters and how it can affect type inference in your functions.
Learn how to use corepack
to configure package managers in Node.js projects, ensuring you always use the correct one.
Learn how to strongly type process.env in TypeScript by either augmenting global type or validating it at runtime with t3-env.
Discover when it's appropriate to use TypeScript's any
type despite its risks. Learn about legitimate cases where any
is necessary.
Learn why TypeScript's types don't exist at runtime. Discover how TypeScript compiles down to JavaScript and how it differs from other strongly-typed languages.