Explain covariance and contravariance for function types. Why are method parameters bivariant in TypeScript, and what does `strictFunctionTypes` change?
Deep type-system reasoning about when one function type is assignable to another.
Variance describes how the compatibility of a function follows the compatibility of its parts. Function RETURN types are covariant: a `() => Dog` is assignable where `() => Animal` is expected (returning something more specific is safe). Function PARAMETER types are contravariant under sound rules: a handler `(a: Animal) => void` is assignable where `(d: Dog) => void` is expected — it accepts anything the callee might pass, so it's safe; the reverse (a Dog-only handler used where any Animal may arrive) is unsound. `strictFunctionTypes` turns on this contravariant checking for function-typed parameters. The catch: it does NOT apply to METHODS declared with method shorthand (`m(x: T): void`) — those stay BIVARIANT (assignable both directions) deliberately, because otherwise generic collections like `Array<T>` (whose methods take T) would become painfully un-assignable, and because a lot of existing DOM/event typings rely on it. So `(x: Dog) => void` written as a property is checked strictly, but the same signature as a method is not. Practical upshot: prefer property-style function fields (`onEvent: (e: E) => void`) over method shorthand when you want the compiler to catch unsafe handler substitutions — and know that event-handler assignability 'just working' often rests on method bivariance rather than being truly sound.
Reasoning about handler/callback assignability, designing generic APIs, choosing method vs property function fields, debugging why a callback type does or doesn't error.