This type:
interface NumberOrStringDictionary {
[index: string]: number | string;
length: number;
name: string;
}
implies that a value of type NumberOrStringDictionary must contain a property named length of type number a property named name of type string. Additionally, a value of that type may contain any string-named properties, as long as the values of such properties are assignable to string | number.
A few points to clarify:
Index signatures may not conflict with any other keys. For example, the length property is of type number, so it is compatible with the index signature (number is assignable to number | string). And the name property is also compatible. If you tried to add a property of some incompatible type, you'd get an error:
interface Oops extends NumberOrStringDictionary {
acceptable: boolean; // error!
//~~~~~~~~ <-- boolean not assignable to string | number
}
If I have a key of type string and I read the property at that key from a value of type NumberOrStringDictionary, what is the type of that property? It is number | string. Now imagine what would happen if you were allowed to add an incompatible property (like acceptable: boolean). In that case it would no longer be true. If the key I choose happens to be "acceptable", then a boolean comes out which is not number | string, so something is wrong. To be safe, you'd have to expect number | string | boolean.
You can think of the non-index signature properties as being special cases of the index signature, where you know the property key definitely exists and that the value is of some possibly more specific type. The known properties should be thought of as particular/special cases of the indexer. "All properties of this object are of type string | number. In particular, the length property of this object is of type number, and the name property of this object is of type string". It's like saying "My pets are all dogs. Fido here is a poodle." Incompatibility would look like "My pets are all dogs. Fido here is a parakeet". You can say that, but it's not consistent. You might be thinking of index signatures as a "default" or "everything else" or "rest" case, ("My pets are all dogs except for this parakeet") but that's not what it means in TypeScript. There is an open issue, microsoft/TypeScript#17867 asking for such a construct in the language, but it is not yet there (there are workarounds).
An index-signature does not generally mean that all string-keyed properties will appear. You are allowed to put as many or as few such properties as you'd like. It is therefore possible that when you inspect a property of type NumberOrStringDictionary, it will be missing. When you read such a missing property, you will get a value of type undefined, not of string | number, but the compiler will pretend that it is string | number. TypeScript 4.1 introduced a compiler flag to treat undefined as a possible result of any index signature property read, but it is not on by default.
Perhaps it would be helpful to go through some examples and see what is accepted and what is rejected by the compiler. Here's a valid assignment:
const valid: NumberOrStringDictionary = {
length: 1,
name: "bob",
someOtherKey: 123,
someOtherKey2: "hey"
};
The requisite length and name properties are in there, and the additional properties match the index signature. This is also valid:
const alsoValid: NumberOrStringDictionary = {
length: 1,
name: "bob",
};
because you don't have to add any such index-signature properties. Now for the mistakes:
const invalid1: NumberOrStringDictionary = { // error!
//~~~~~~~~ <- property "name" is missing
length: 1,
someOtherKey: 123,
someOtherKey2: "key"
};
You can't leave out a required property. Also:
const invalid2: NumberOrStringDictionary = {
length: "fred", // error!
//~~~~ <-- string is not assignable to number
name: "bob",
someOtherKey: 123,
someOtherKey2: "key"
};
You can't put the wrong type of the required property. And finally:
const invalid3: NumberOrStringDictionary = {
length: 1,
name: "bob",
someOtherKey: 123,
someOtherKey2: true // error!
//~~~~~~~~~~~ <-- boolean is not string | number
};
You can't add a property that conflicts with the index signature.
Playground link to code