Electric vans are vehicles. Does it follow that a roster of electric vans is a roster of vehicles?
answer
- start with the relation, not a keyword
- three answers, not two
- the container type decides
- covariant, contravariant, invariant
- invariance is the common default
basics
~20 sNot automatically. A subtype relation between two element types says nothing on its own about the containers over them; the container type decides. Three answers are possible - covariant, contravariant, invariant - and invariance is the usual default.
solid answer
~40 sThe relation between elements does not propagate to containers for free. Given that `ElectricVan` is a subtype of `Vehicle`, there are exactly three possible answers for a container type `Roster<T>`: **covariant**, meaning `Roster<ElectricVan>` is a subtype of `Roster<Vehicle>`; **contravariant**, meaning the relation reverses and `Roster<Vehicle>` is a subtype of `Roster<ElectricVan>`; or **invariant**, meaning neither is a subtype of the other and only a roster whose element argument is exactly `Vehicle` is accepted. Many type systems make invariance the default and require the author of the container to opt into a direction, because which answer is sound depends on the container itself rather than on the elements. So the honest interview answer is: it does not follow, it depends on how `Roster` is defined, and there are three candidate relations to choose between.
code
pseudocode · 11 linestype Vehicle
type ElectricVan is a Vehicle
function auditAll(fleet: Roster<Vehicle>)
for each v in fleet
record(v)
vans: Roster<ElectricVan> = loadVanRoster()
auditAll(vans) // accepted only if Roster<ElectricVan>
// is a subtype of Roster<Vehicle>go deeper
Recall that a subtype relation between elements does not automatically relate the containers, and be able to name the three possible answers without hesitating.
Explain each of the three relations precisely in terms of which type is a subtype of which, and state that the container's own definition is what supplies the answer.
Show that you read a rejected argument as one of the three answers being reported, and that you can name the sound options available at that call site.
Frame the choice as an API commitment: the direction a shared container publishes shapes every downstream signature, and revisiting it later is a cross-team change.
## The question on the table A parcel carrier's dispatch code has a type `Vehicle` and a type `ElectricVan` that is a subtype of it: every electric van is a vehicle. A routine is declared to take a roster of vehicles, `auditAll(fleet: Roster<Vehicle>)`. You are holding a `Roster<ElectricVan>`. May you pass it? That is the **substitutability question**, and it is where every variance conversation starts: *given a subtype relation between two element types, what relation holds between the containers over them?* Notice what the question is not. It is not about the objects in the roster - each of those really is a vehicle. It is about two **types**, `Roster<ElectricVan>` and `Roster<Vehicle>`, and whether one may stand in for the other at a call site. ## The relation does not travel for free The naive expectation is that subtyping is inherited by anything built over it: vans are vehicles, so rosters of vans are rosters of vehicles. Type systems do not grant that. A container is not just a bag of elements to a type checker; it is a **surface of operations** phrased in terms of its element type. Substitution is only sound if everything the caller may legitimately do through the expected type remains sound when the actual type is handed over instead. Whether that holds differs from container to container, so the relation is decided by the container's own definition, not by the element relation alone. (Which of the three answers a particular container earns, and how that is derived, is a separate subject from this one - here the point is only that the question has three possible answers and that the container is what answers it.) ## Exactly three answers | Answer | Relation, given `ElectricVan` is a subtype of `Vehicle` | What may be passed where `Roster<Vehicle>` is expected | |---|---|---| | **Covariant** | `Roster<ElectricVan>` is a subtype of `Roster<Vehicle>` | a roster of vehicles, or of any vehicle subtype | | **Contravariant** | `Roster<Vehicle>` is a subtype of `Roster<ElectricVan>` | a roster of vehicles, or of any vehicle supertype | | **Invariant** | no subtype relation in either direction | only a roster whose element argument is exactly `Vehicle` | Three observations about that table are worth stating out loud in an interview: - **All three permit the exact argument.** `Roster<Vehicle>` is always acceptable where `Roster<Vehicle>` is expected; invariance is not a restriction on the obvious call. - **All three permit ordinary subtyping of the container itself.** If some `VehicleLog` type is declared to be a `Roster<Vehicle>`, it is accepted under any of the three answers, because the element argument did not change. - **Covariance and contravariance are mirror images**, not degrees of the same thing. One follows the element relation, the other reverses it. ## What the answer is a property of 1. **It is a property of a parameter position, not of a type.** A container with two parameters can answer differently for each, so "is this container covariant?" is an incomplete question; ask which parameter. 2. **It is a property of the type, not of the value.** Two rosters holding exactly the same objects can be related or unrelated depending only on how their types were written down. 3. **It is decided statically.** Nothing is converted, copied or re-tagged when a substitution is permitted; the same container is simply viewed through a different type at the call site. 4. **It does not arise at all without an element subtype relation.** If two element types are unrelated, no variance answer makes their containers related. ## Where candidates go wrong The most common failure is answering with confidence in either direction - "obviously yes, vans are vehicles" or "no, containers are never related" - when the correct first move is to say the relation is a choice with three possible values. A second failure is treating a rejected argument as a defect in the tooling rather than as one of the three answers being reported back. A third is conflating the element objects with the container types: the fact that every element genuinely is a vehicle is precisely what makes the question non-trivial, not what settles it. ## What the interviewer asks next Having heard the three answers, the interviewer almost always moves to who gets to decide which one applies and how that decision is derived from the container. That is the depth probe; this question is the setup for it, and getting the setup crisp - three answers, decided by the container, invariance as the common default - is what a junior candidate is expected to deliver.
- If the substitution is permitted, is anything converted when the roster is passed?No. Permission is a statement about types, resolved at the call site; the same container object is handed over and simply viewed through the expected type. No element is copied, wrapped or re-tagged, and no run-time work happens because of the substitution.
- Can one container type give different answers for different type parameters?Yes. The question is asked per parameter position, so a container parameterised by both a key type and a value type can be covariant in one and invariant in the other. "Is this type covariant?" is therefore incomplete - name the parameter you mean.
- Does the question arise when the two element types are unrelated?No. The substitutability question presupposes a subtype relation between the element types. If neither element type is a subtype of the other, no variance answer relates the containers, and the call is rejected under all three.
A haulage permit that names electric vans is not automatically a permit that names vehicles. Whether one stands in for the other is written into the permit, not deduced from the fact that vans are vehicles.
saying these in an interview costs you the question
- Assuming element subtyping always carries over to the container
- Claiming containers are never related, whatever the element types
- Believing the elements get converted when the substitution is permitted
- Treating covariance and contravariance as strength levels of one direction
- Answering from a remembered keyword rather than the relation being asked about