skip to content

At a call site expecting a roster of vehicles, what does each of the three variance answers permit?

level: middleimportance: must knowfreq 58%

answer

  1. read the expected parameter type first
  2. build a chain of three element types
  3. downward for one, upward for the other
  4. the exact argument always passes
  5. one answer per parameter position

basics

~20 s

Covariance admits a roster of any vehicle subtype, contravariance admits a roster of any vehicle supertype, and invariance admits only a roster whose element argument is exactly the vehicle type. All three admit that exact roster.

solid answer

~40 s

Take a chain of element types: a leased asset is the general thing, a vehicle is a leased asset, and an electric van is a vehicle. A routine expects `Roster<Vehicle>`. If the roster type is **covariant** in its element parameter, a `Roster<ElectricVan>` is accepted - substitution follows the element relation downward. If it is **contravariant**, a `Roster<LeasedAsset>` is accepted instead - the relation reverses, so wider element arguments stand in. If it is **invariant**, only `Roster<Vehicle>` itself is accepted. In all three cases a `Roster<Vehicle>` works, and in all three a type declared to *be* a `Roster<Vehicle>` works, because those need no variance at all. The answer is per parameter position, so a container with several parameters can license different substitutions on each.

code

pseudocode · 7 lines
pseudocode
// leased asset  >  vehicle  >  electric van

function auditAll(fleet: Roster<Vehicle>)

auditAll(rosterOfVehicles)      // accepted under all three answers
auditAll(rosterOfElectricVans)  // accepted only if covariant in the element position
auditAll(rosterOfLeasedAssets)  // accepted only if contravariant in the element position

go deeper

for a junior

Learn the three licences as three sets of acceptable arguments, and remember that the exact element argument is accepted in every one of them.

for a middle

Explain each licence with a three-link element chain and state which direction it travels, then note that the question is asked per parameter position.

for a senior

Turn the answer into an API statement: the direction you publish decides which callers can reach your routine without building a container first.

for a principal

Weigh the licence against the callers you want to serve across teams, and treat a later change of direction as a published-contract change, not a local edit.

## Set the chain up first The question is unanswerable without a chain of element types, so start by naming one. In a carrier's fleet model: a **leased asset** is the broad category, a **vehicle** is a leased asset, and an **electric van** is a vehicle. Now a routine is declared `auditAll(fleet: Roster<Vehicle>)`. The expected type is fixed. What varies is the answer the roster type gives for its element parameter, and each answer licenses a different set of arguments at that call site. ## The three licences | Answer for the element position | Accepted at `auditAll(fleet: Roster<Vehicle>)` | Rejected | |---|---|---| | **Covariant** | `Roster<Vehicle>`, `Roster<ElectricVan>` | `Roster<LeasedAsset>` | | **Contravariant** | `Roster<Vehicle>`, `Roster<LeasedAsset>` | `Roster<ElectricVan>` | | **Invariant** | `Roster<Vehicle>` only | `Roster<ElectricVan>`, `Roster<LeasedAsset>` | Read the table as a statement about **who may call**, because that is what an interviewer is testing. Covariance widens the set of callers downward along the element hierarchy: anyone holding a roster of some specific kind of vehicle may call. Contravariance widens it upward: anyone holding a roster of something more general than a vehicle may call. Invariance widens it not at all. ## What every answer permits regardless Three cases are accepted under all three answers, and candidates who miss them sound as if invariance means "nothing works": - **The exact instantiation.** `Roster<Vehicle>` is accepted trivially; a type is a subtype of itself. - **A subtype of the container that keeps the element argument.** If a `VehicleLog` type is declared to be a `Roster<Vehicle>`, it is accepted by ordinary subtyping - the element argument never changed, so no variance step is needed. - **Anything the routine's own signature was widened to accept**, if the author chose to phrase the parameter more loosely. That is a decision recorded somewhere in the code, not something the call site invents. ## The answer is per position, not per type A container parameterized over two types - say a schedule keyed by depot and holding vehicles - poses the question twice, once per parameter, and may answer differently each time. So "is a schedule covariant?" is an incomplete question; the askable form names the position: "is the schedule covariant **in its value parameter**?" This is also why one container can accept a narrower argument in one slot and demand an exact match in another, which looks arbitrary until you see that two independent questions were answered. ## Nothing happens at run time A licence to substitute is not a conversion. When a `Roster<ElectricVan>` is accepted where a `Roster<Vehicle>` is expected under covariance, the same container is handed over; nothing is copied, no element is boxed or re-tagged, and no check runs on entry to the routine. The call site simply views one object through a different static type. That matters practically: permitted substitution has no cost, whereas the workaround for a *refused* substitution - building a new container - has a real one. ## How to answer this in an interview Strong candidates answer with the chain and the three sets, in that order, and they say explicitly which direction each answer travels rather than reciting the words covariant and contravariant. A useful closing sentence is the practical one: **the answer tells you who can call your routine.** If the routine should serve every caller holding a roster of some specific vehicle kind, you need the covariant licence; if it should serve callers holding broader rosters, you need the contravariant one; if you cannot have either, callers must hand you a roster of exactly the expected element type or build one. ## Common wrong turns 1. **Giving contravariance the covariant licence** - saying a roster of electric vans is accepted under contravariance. It is the roster of leased assets that is accepted; contravariance reverses the direction. 2. **Treating invariance as "uncallable"** - forgetting that the exact instantiation and container subtypes over it still pass. 3. **Answering for the type instead of the position** when the container has more than one parameter. 4. **Assuming a conversion happens** when the substitution is permitted, which leads to imagined per-call costs.

  • Under invariance, is there anything other than the exact instantiation that still type-checks?
    Yes. Any type declared to be a roster of vehicles - a named log type that extends or implements it at that element argument - is accepted by ordinary subtyping, because the element argument is unchanged. Invariance blocks a different element argument, not container subtyping.
  • If a container has two type parameters, how many substitutability questions are there?
    One per parameter position, answered independently. A container can be covariant in one parameter and invariant in another, which is why the precise question always names the position rather than asking whether the type as a whole is covariant.
  • What runs at the call site when a covariant substitution is accepted?
    Nothing. The permission is resolved statically and the same container object is passed; no copy, wrapper or entry check is created. The cost appears only when a substitution is refused and you build a new container to get around it.

saying these in an interview costs you the question

  • Saying contravariance accepts a roster of a narrower element type
  • Claiming invariance makes the routine impossible to call
  • Answering for the whole type when the container has several parameters
  • Assuming a permitted substitution copies or wraps the container
  • Naming the direction by keyword without saying which type is the subtype