A friend of mine has recently intrigued me with a question about TypeScript.

He asked:

Why TypeScript is not consistent in the following situation?

Imagine you have a type Foo and type Bar which extends Foo.

Why, when passing instance of Bar into a function accepting Foo, TypeScript does not complain, but when we try to declare a variable of type Foo but with extra keys specified, it does complain.

An example:

interface Foo {
  x: number;
}

interface Bar extends Foo {
  y: string;
}

function acceptingFoo(arg1: Foo): void {}

const foo: Foo = {
  x: 1234,
};

const bar: Bar = {
  x: 1234,
  y: "value",
};

// Why can you pass an instance of `Bar` into a function accepting `Foo`
// and TypeScript won't complain?
acceptingFoo(bar);
//           ^ No error here.

// But you cannot declare an object of type Foo with extra keys.
const barWithFooType: Foo = {
  x: 1234,
  y: "value",
  //^ TypeScript complains about line above:
  // Object literal may only specify known properties, and 'y' does not exist in type 'Foo'.(2322)
};

After some research I found this answer on Stack Overflow.

In TypeScript, when you declare an object literal, it is initially considered “fresh”.

No matter if you construct it when calling a function or when declaring a variable using it, object literal freshness is always enforced by compiler.

To check that I’ve run TS compiler over the following code.

acceptingFoo({
  x: 1234,
  y: "value",
  //^ TypeScript has thrown an error here:
  // Object literal may only specify known properties, and 'y' does not exist in type 'Foo'.(2322)
});

And it worked as I suspected - TS complained about passing an object literal with not known property. Bingo!

It turned out the issue here was not about inconsistency between typing in declaration vs typing of a function arguments, but about object literals freshness.

The rationale behind this behaviour is that it prevents possible typing errors when declaring object literals.

For example, check the following code:

interface Options {
  color?: string;
}

function acceptingOptions(arg1: Options): void {}

acceptingOptions({ colour: "black" });
//                ^ Typo here, should be `color`.
//                  TypeScript will throw an error.

Without freshness, TypeScript would not spot this bug.

If you want to read more about freshness of object literals, check a PR that adds this feature to TypeScript compiler. You can also read a message about freshness authored by one of TS contributors.

Thanks for reading!