9

Why empty interface doesn't require object to be empty?

interface A {};
const a: A = {a: 1};
console.log(a);

is valid code and will output { a: 1 }.

I would assume that adding optional property should work fine, but

interface A {};
interface B extends A {
    b?: any;
}
const a: B = {a: 1};
console.log(a);

ends with error Type '{ a: number; }' is not assignable to type 'B'.

  • If interface define what properties object must have, B case should work fine, all required properties are present.
  • If interface define what properties object can have, A case should result in error, a is not defined in interface.

Non empty interface defines both what object can and must have. Empty interface behaves like any.

Is there explanation why empty interface behaves like this? Is this intentional or just a bug?

1
  • Empty interface doesn't define any constraint and is applicable to any object. I'm not sure about its utility though. Commented Mar 1, 2017 at 17:47

4 Answers 4

10

This behavior is intentional.

The excess property check is not performed when the target is an empty object type since it is rarely the intent to only allow empty objects.


Actually, you can assign {a: 1} to B, the other answers here are mostly wrong.

You have stumbled upon another slightly confusing quirk in TypeScript, namely that you can not directly assign an object literal to a type where the object literal contains other properties than the one specified in the type.

However, you can assign any existing instance of an object to a type, as long as it fulfill the type.

For example:

interface Animal {
    LegCount: number;
}

let dog: Animal = { LegCount: 4, Fur: "Brown" }; // Nope

var myCat = { LegCount: 4, Fur: "Black" };
let theCat: Animal = myCat; // OK

This constraint is simply ignored whey you have a type that is empty.

Read more here and here.

A later answer from the Typescript team is available on GitHub.

Sign up to request clarification or add additional context in comments.

5 Comments

Thank you for links. But this explanation is not complete. Why case A where I directly assign literal with properties not specified in interface works?
@mleko Because A is empty. So TS does not care about the rule that you only can declare known properties in the object literal on direct assignment.
But why empty interface is so special? Is it intentional(if yes, what was the intention) or it just happen to work this way?
@mleko As far as I know, it's intentional. And I believe that the idea behind that is something like: Empty types are basically useless, you won't be able to use them in any meaningful way with respect to typing. So the whole point of making sure that you can't assign object literals with properties that does not exist in the type, preventing you from declaring properties that would just disappear type-wise, is irrelevant since you can't really use the target type at all. So the compiler just don't bother. I might be wrong, but that is my best educated guess.
"The excess property check is not performed when the target is an empty object type since it is rarely the intent to only allow empty objects in that case." — is that so? Why would I then go out of my way to make an empty interface? How/when would that be useful unless I mean "empty object, and please perform excess check." Anyway, if the empty interface means "any shape; skip the excess property check for literals," then to specify an empty object I do type EmptyObject = {}?
3

Okay, interesting question.

Test Case 1:

interface A {};
interface B extends A {
    b?: any;
}
const a: A = {a: 1};
console.log(a);

It passes, as A is an empty interface, whatever you dumb inside is going to return back. Works like a class Object

Test Case 2:

interface A {};
interface B extends A {
    b?: any;
}
const a: A = {b: 1};
console.log(a);

Just changed the value a to b just to prove first test case. It passes.

Test Case 3:

interface A {};
interface B extends A {
    b?: any;
}
const a: B = {a: 1};
console.log(a);

It fails, because there is no prop inside interface A or B that is named a

Test Case 4:

interface A {};
interface B extends A {
    b?: any;
}
const a: B = {b: 1};
console.log(a);

It passes, as interface B has a prop named b

I hope this helps you understand.

PS: prop refers to property.

Take this as an analogy: Interface A is a empty house, dumb whatever you want. It wont utter a word. Interface B is a house in a colony, which means it needs behave specifically to size, shape and needs to stick to Interface A. Which means Interface B is not more a empty and is restricted with rules.

Comments

2

This is the way structural typing works in Typescript. It is based on structural subtyping.

In short

An instance of B is compatible with A if B implements all the members required by A.

Since A does not require any member, all objects are compatible with A.

Full details in this documentation

5 Comments

B also does not require any member, but B case result in error. Interface not only require but also restrict. You can use only properties defined in interface. IMHO since A doesn't define any property you shouldn't be able to use one.
Unfortunately, that is your opinion, not the way typescript type system works :). There is no such thing as "restriction"
If there is no restriction why B case result in error? If interface only require what properties object should have B case should work fine, I defined all required properties.
To quote Ryan Cavanaugh: 'The problem is that extra property checking doesn't occur as a result of contextual typing, it happens during regular assignability when the object type is "fresh"'. So it is only when you assign a "fresh" object as is the case is B that this check happens. Structural typing happens the way in described in the link above. I realize this is confusing and is the source of multiple issues on the typescript GitHub project
Structural typing has nothing to do with the problem described by OP. The code in the question would be absolutely fine in regards to just the type system in TS.
2

Yes, there is an explanation in the official docs. Nope not a bug, but surprising behavior. This article microsoft/TypeScript - Why are all types assignable to empty interfaces? explains the behavior.

That documentation also adds as an aside: "In general, you should never find yourself declaring an interface with no properties."

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.