Consider the different types listed below:
type Person = {
id: string;
name: string;
};
interface PeopleRepository {
getPerson(query: { id: string }): Person;
}
class Repository implements PeopleRepository {
getPerson({ id, age }: { id: string; age: number }) {
return { id, age: age };
}
}
const analyzeSituation = () => {
const repository: PeopleRepository = new Repository();
console.log(repository.getPerson({ id: "foo" }));
};
analyzeSituation();
It is important to note that the implementation method requires two properties in the input object: id
and age
, whereas the interface only necessitates id
.
Surprisingly, this code does not generate any compilation errors!
Nevertheless, when executed, the code will encounter issues because the call to repository.getPerson
fails to provide an age
in its argument, resulting in it being undefined
. Consequently, the line age = age
will throw an error as age
is undefined
at runtime. This discrepancy highlights a conflict between TypeScript's assumptions and actual runtime behavior.
But why does TypeScript permit this?
Just to clarify: I am aware that this implementation contradicts the Liskov Substitution Principle (LSP). Typically, one would expect TypeScript to raise a red flag due to this violation by indicating an error during the declaration of the getPerson
implementation. Specifically, warning that the method cannot adhere to the interface specifications since it mandates fields in the argument that are absent from the interface's signature.