Bird
Raised Fist0
Typescriptprogramming~10 mins

Covariance and contravariance in Typescript - Step-by-Step Execution

Choose your learning style10 modes available
Concept Flow - Covariance and contravariance
Start with types
↓
Check assignment compatibility
↓
Covariance: output types compatible?
Yes→Allow assignment
↓
Error
↓
Contravariance: input types compatible?
Yes→Allow assignment
No↓
Error
This flow shows how TypeScript checks if one type can be assigned to another by comparing output types (covariance) and input types (contravariance).
Execution Sample
Typescript
interface Animal { name: string }
interface Dog extends Animal { bark(): void }

let dogFunc: (d: Dog) => Dog;
let animalFunc: (a: Animal) => Animal;

animalFunc = dogFunc; // Contravariance check

dogFunc = animalFunc; // Covariance check
This code tests assigning functions with parameters and return types to see covariance and contravariance in action.
Execution Table
StepActionInput Type CheckOutput Type CheckAssignment AllowedReason
1Assign dogFunc to animalFuncDog parameter to Animal parameter?Dog return to Animal return?YesParameter types must be contravariant (Animal assignable to Dog), and Dog is assignable to Animal for return types
2Assign animalFunc to dogFuncAnimal parameter to Dog parameter?Animal return to Dog return?NoParameter types must be contravariant (Dog not assignable to Animal), or return types must be covariant (Animal not assignable to Dog)
3Assign compatible function typesParameter types contravariantReturn types covariantYesBoth input and output types satisfy variance rules
4End---No more assignments to check
💡 Assignments stop when type compatibility rules fail or all checks pass
Variable Tracker
VariableStartAfter Step 1After Step 2After Step 3Final
dogFuncfunction (Dog) => Dogunchangedunchangedassigned animalFunc if compatiblefinal assigned value
animalFuncfunction (Animal) => Animalassigned dogFuncunchangedunchangedfinal assigned value
Key Moments - 2 Insights
Why can't we assign a function with parameter type Dog to a variable expecting a function with parameter type Animal?
Because function parameters are contravariant, the assigned function must accept all inputs the variable expects. A Dog parameter is more specific than Animal, so it can't accept all Animals. See execution_table step 2.
Why can't we assign a function returning Animal to a variable expecting a function returning Dog?
Because return types are covariant, the assigned function must return a type compatible with the variable's expected return. Animal is more general than Dog, so it may return something missing Dog's features. See execution_table step 2.
Visual Quiz - 3 Questions
Test your understanding
Look at the execution table, at which step is the assignment rejected due to parameter type incompatibility?
AStep 1
BStep 2
CStep 3
DStep 4
💡 Hint
Check the 'Input Type Check' and 'Assignment Allowed' columns in execution_table rows
According to variable_tracker, what is the state of dogFunc after step 2?
AUnchanged
BUndefined
CAssigned animalFunc
DNull
💡 Hint
Look at dogFunc row and After Step 2 column in variable_tracker
If we change Dog to extend Animal with no extra methods, how would step 2's output type check change?
ANo change, assignment rejected
BAssignment still rejected due to parameter types
CAssignment allowed because return types match
DAssignment allowed due to contravariance
💡 Hint
Consider covariance rules for return types in execution_table step 2
Concept Snapshot
Covariance and contravariance control function type assignments.
- Covariance: output (return) types can be more specific.
- Contravariance: input (parameter) types can be more general.
- TypeScript enforces these rules to keep assignments safe.
- Function parameters are contravariant; returns are covariant.
- Violations cause assignment errors.
Full Transcript
This visual execution shows how TypeScript checks function type assignments using covariance and contravariance. It starts by comparing input parameter types contravariantly and output return types covariantly. The execution table traces assignment attempts step-by-step, showing when assignments fail due to incompatible parameter or return types. The variable tracker records how function variables change or stay the same after each step. Key moments clarify why parameter types are contravariant and return types covariant, helping beginners avoid confusion. The quiz tests understanding by referencing the execution table and variable tracker. The snapshot summarizes the core rules for safe function type assignments in TypeScript.

Practice

(1/5)
1. Which statement best describes covariance in TypeScript?
easy
A. It allows replacing a type with a more specific type safely, mainly in outputs.
B. It allows replacing a type with a more general type safely, mainly in inputs.
C. It means types cannot be replaced or changed in any way.
D. It only applies to primitive types like string and number.

Solution

  1. Step 1: Understand covariance meaning

    Covariance means you can use a more specific type where a general type is expected, usually for outputs like return values.
  2. Step 2: Compare with contravariance

    Contravariance is about inputs and generalizing types, so it is different from covariance.
  3. Final Answer:

    It allows replacing a type with a more specific type safely, mainly in outputs. -> Option A
  4. Quick Check:

    Covariance = more specific output type [OK]
Hint: Covariance = output types get more specific [OK]
Common Mistakes:
  • Confusing covariance with contravariance
  • Thinking covariance applies to inputs
  • Believing covariance restricts type changes
2. Which of the following TypeScript function types correctly demonstrates contravariance in the parameter type?
easy
A. (input: Dog) => void
B. (input: Animal) => void
C. (input: string) => void
D. (input: number) => void

Solution

  1. Step 1: Understand contravariance in parameters

    Contravariance means a function can accept a more general type as input than expected.
  2. Step 2: Compare parameter types

    Given Dog extends Animal, accepting Animal (more general) instead of Dog is contravariant.
  3. Final Answer:

    (input: Animal) => void -> Option B
  4. Quick Check:

    Contravariance = input type generalizes [OK]
Hint: Contravariance = input types get more general [OK]
Common Mistakes:
  • Choosing more specific input types for contravariance
  • Mixing input and output type variance
  • Ignoring inheritance relationships
3. Consider the following TypeScript code:
class Animal { name: string = 'animal'; }
class Dog extends Animal { breed: string = 'bulldog'; }

let func1: (dog: Dog) => Dog = (d) => d;
let func2: (animal: Animal) => Animal = func1;

const result = func2(new Animal());
console.log(result.name);

What will be the output when this code runs?
medium
A. animal
B. bulldog
C. undefined
D. Type error at assignment

Solution

  1. Step 1: Analyze function type assignment

    func1 expects a Dog parameter and returns Dog. Assigning func1 to func2 which expects Animal parameter and returns Animal is invalid because parameter types are contravariant and return types covariant.
  2. Step 2: Understand TypeScript type checking

    TypeScript disallows assigning a function expecting a more specific parameter (Dog) to one expecting a more general parameter (Animal) due to contravariance rules.
  3. Final Answer:

    Type error at assignment -> Option D
  4. Quick Check:

    Contravariance blocks incompatible parameter types [OK]
Hint: Check parameter types for contravariance errors [OK]
Common Mistakes:
  • Assuming assignment is allowed without error
  • Ignoring contravariance in function parameters
  • Expecting runtime output instead of compile error
4. The following TypeScript code causes a type error. What is the main cause?
interface Producer<out T> {
  produce: () => T;
}

interface Consumer<in T> {
  consume: (item: T) => void;
}

let producer: Producer<Animal> = { produce: () => new Animal() };
let consumer: Consumer<Dog> = { consume: (d) => console.log(d.breed) };

consumer = producer; // Error here
medium
A. Dog does not extend Animal, so types are incompatible.
B. The produce method is missing a return type.
C. Assigning Producer<Animal> to Consumer<Dog> is invalid due to variance mismatch.
D. The consume method should return a value.

Solution

  1. Step 1: Identify variance roles

    Producer is covariant (outputs), Consumer is contravariant (inputs). Assigning producer to consumer mixes these roles incorrectly.
  2. Step 2: Understand type incompatibility

    Producer<Animal> cannot be assigned to Consumer<Dog> because their variance directions conflict, causing a type error.
  3. Final Answer:

    Assigning Producer<Animal> to Consumer<Dog> is invalid due to variance mismatch. -> Option C
  4. Quick Check:

    Variance mismatch causes assignment error [OK]
Hint: Match variance directions when assigning generic types [OK]
Common Mistakes:
  • Ignoring variance annotations (in/out)
  • Assuming unrelated interfaces can be assigned
  • Confusing input and output roles
5. You want to create a TypeScript function that accepts a callback which can handle any subtype of Animal as input and returns a more specific subtype of Animal. Which function type signature correctly uses covariance and contravariance to allow this flexibility?
hard
A. function processAnimal(callback: (input: Animal) => Dog): Dog
B. function processAnimal(callback: (input: Dog) => Animal): Animal
C. function processAnimal(callback: (input: Animal) => Animal): Dog
D. function processAnimal(callback: (input: Dog) => Dog): Animal

Solution

  1. Step 1: Identify input contravariance

    The callback input should be a general type (Animal) to accept any subtype safely (contravariance).
  2. Step 2: Identify output covariance

    The callback returns a more specific subtype (Dog), allowing covariance in the return type.
  3. Step 3: Match function signature

    function processAnimal(callback: (input: Animal) => Dog): Dog matches these rules: input is Animal (general), output is Dog (specific), and the function returns Dog.
  4. Final Answer:

    function processAnimal(callback: (input: Animal) => Dog): Dog -> Option A
  5. Quick Check:

    Input general, output specific = contravariant input, covariant output [OK]
Hint: Input general, output specific for flexible callbacks [OK]
Common Mistakes:
  • Reversing input and output types
  • Using more specific input types causing errors
  • Ignoring return type covariance
Recommended

Ready to prove it?

Pass the test and this pattern moves unknown strong while earning +1 Grip toward your certificate.

10 questions~5 mineasy hard