A function type `(Animal) → Dog` and a function type `(Dog) → Animal` are both in scope. Under correct subtyping rules with contravariant parameters and covariant returns, which is a subtype of which?
A`(Dog) → Animal` is a subtype of `(Animal) → Dog`, because it accepts a narrower input and returns a broader output
B`(Animal) → Dog` is a subtype of `(Dog) → Animal`, because it accepts a broader input and returns a narrower output
CNeither is a subtype of the other; function types are invariant
DBoth are subtypes of each other, since they accept the same types in different directions
Function subtyping is contravariant in parameters and covariant in returns. `(Animal) → Dog` <: `(Dog) → Animal` because: (1) its parameter type `Animal` is a supertype of `Dog` — a caller passing a `Dog` is safe since the function accepts any `Animal`; (2) its return type `Dog` is a subtype of `Animal` — a caller expecting an `Animal` result is safe since the function always returns a `Dog`. The tempting wrong answer (option A) reverses this logic: a function that *only* accepts `Dog` is more restrictive, not less, from the caller's perspective.
Question 2 Multiple Choice
What does an upper bound `T extends Comparable<T>` on a generic type parameter primarily enable?
AIt prevents T from being instantiated with primitive types, improving runtime performance
BIt allows the compiler to guarantee that values of type T have a compareTo method, enabling type-safe generic operations like sorting
CIt restricts T to exactly the Comparable type, removing the benefit of generics
DIt forces callers to provide explicit type annotations rather than relying on inference
An upper bound tells the type checker: 'T must be a subtype of Comparable<T>.' By the Liskov Substitution Principle, anything T can do, Comparable<T> can do — so the compiler knows values of type T have a compareTo method. This enables writing generic algorithms (sorting, priority queues) that work with any comparable type while remaining fully type-safe. Without the bound, T is completely opaque: you cannot call any methods on it. The bound adds capability knowledge, not restriction — it's what makes the generic useful.
Question 3 True / False
If `Dog` is a subclass of `Animal`, then `List<Dog>` is a subtype of `List<Animal>` in a type-safe generic system.
TTrue
FFalse
Answer: False
Generic collections are typically invariant in type-safe systems precisely because covariance would allow unsafe operations. If `List<Dog>` were a subtype of `List<Animal>`, you could write code like: `List<Animal> animals = new List<Dog>(); animals.add(new Cat());` — which would insert a Cat into a list that holds only Dogs, causing a runtime type error. Java's generic collections (unlike arrays) are invariant to prevent this. Covariance is only safe for read-only (producer) contexts, which is why Java uses wildcards like `? extends Animal` for that use case.
Question 4 True / False
In a structural type system, a type can be a subtype of another type even without any explicit declaration of inheritance or interface implementation.
TTrue
FFalse
Answer: True
Structural subtyping is based on shape (what fields and methods a type has) rather than declared relationships. TypeScript is the canonical example: if type A has all the properties that type B requires, A is structurally a subtype of B, even if A never mentions B. This is sometimes called 'duck typing' at the type-system level. Nominal type systems (like Java or C#) require explicit declaration (`implements`, `extends`). Both approaches enforce the core subtyping guarantee — a subtype can do everything a supertype can — but through different mechanisms.
Question 5 Short Answer
Why must function types be contravariant in their parameter types? Give a concrete example showing what goes wrong if parameter types were covariant instead.
Think about your answer, then reveal below.
Model answer: If function parameters were covariant, a function expecting a subtype (e.g., Dog) could be substituted where a function expecting the supertype (Animal) is needed. A caller might then pass a Cat (which is an Animal but not a Dog), causing a type error at runtime. Example: if `(Dog → void) <: (Animal → void)` under covariance, you could pass a `(Dog → void)` function to code that calls it with any `Animal`. When that code passes a `Cat`, the function tries to use Dog-specific methods on a Cat — a type violation. Contravariance ensures the substituted function accepts at least as broad an input as the original.
Contravariance in parameters is the direction required for safe substitution. A subtype function must be at least as accepting as the supertype function. If a caller is prepared to pass an Animal, the function it calls must be able to handle any Animal — so the function's parameter type must be Animal or a supertype of Animal, not a subtype like Dog.