Function Variance

Method syntax vs property syntax strictness.

TypeScript5 min readConcept 21 of 54

Bivariance vs Contravariance

This is one of the most hidden quirks in TypeScript. When you define a function on an interface, you can write it two ways:

1. Method syntax: update(data: string): void;

2. Property syntax: update: (data: string) => void;

They look identical, but TypeScript treats them completely differently when it comes to type checking subclasses (variance).

The Strictness Gap

Method syntax is **bivariant**. It is historically loose and allows unsafe assignments (e.g. assigning a Dog that only eats Bones to an Animal variable that is allowed to eat any Food).

Property syntax is **contravariant**. It is mathematically strict and will throw an error if you try to make an unsafe assignment.

Syntax Comparison

typescript class Animal {} class Dog extends Animal {} // Method Syntax (Loose / Bivariant) interface EventLocker { lock(target: Animal): void; } // Property Syntax (Strict / Contravariant) interface StrictLocker { lock: (target: Animal) => void; } // A function that only knows how to lock Dogs, not all Animals. const dogLocker = (target: Dog) => {}; // OK: TypeScript allows this unsafe assignment because of method syntax. const locker1: EventLocker = { lock: dogLocker }; // Error: TypeScript blocks this unsafe assignment. // const locker2: StrictLocker = { lock: dogLocker };

Try it

Watch how the loose Method Syntax allows an unsafe function to slip through the type checker, while the strict Property Syntax blocks it at the gate.

// Unsafe function: requires a Dog
const fn = (dog: Dog) => {};

Method Syntax
interface API {
update(a: Animal): void;
}

const a: API = { update: fn };
Property Syntax
interface API {
update: (a: Animal) => void;
}

const a: API = { update: fn };
Method (Loose)
fn(dog)
Passes!
Unsafe assignment allowed.
Property (Strict)
fn(dog)
Error!
Unsafe assignment blocked.

Check yourself

Pick an answer to lock it in, then read why. Getting one wrong is part of how it sticks.

  1. 1Which interface definition provides stricter type checking for function assignments?

Remember this

  • Method syntax (fn(): void) is bivariant (loose).
  • Property syntax (fn: () => void) is contravariant (strict).
  • Property syntax prevents runtime crashes caused by unsafe function arguments.
  • Always prefer property syntax for function definitions on interfaces.

Done with this concept?

Mark it complete to track your progress. No login needed.