Question presented to candidate:
"If a Dog is assignable to an Animal, is a Dog[] assignable to an Animal[]? Is a (d: Dog) => void assignable to an (a: Animal) => void? Explain covariance and contravariance, and where TypeScript is deliberately unsound."
What a strong answer should cover:
- Variance describes how a subtype relation between
DogandAnimalcarries over to a type built from them, such asBox<Dog>andBox<Animal>: same direction (covariant), reversed (contravariant), neither (invariant), or both (bivariant) - Outputs (return types, read-only properties) are covariant; inputs (parameters) are contravariant, because a function that accepts any
Animalcan safely be used where onlyDogs will be passed strictFunctionTypes(TypeScript 2.6, part of--strict) checks function-type parameters contravariantly, but parameters declared with method syntax stay bivariant on purpose- Mutable arrays are treated as covariant, which is unsound: you can push a
Catinto aDog[]through anAnimal[]alias;readonlyarrays are covariant and safe - A generic type that both reads and writes
Tis invariant; TypeScript measures variance from the structure, andin/outannotations (TypeScript 4.7) declare and check it
Clarifying questions expected:
- Is
strictFunctionTypes(orstrict) on in this codebase? - Are the callbacks declared with method syntax or as function-typed properties?
Code / implementation expected: Yes β assign a Dog-only callback where an Animal callback is expected, show the compile error, and show the array hole at run time.