Lesson 34 of 80
Two interface members with the exact same call signature get checked differently — write it as method shorthand and TypeScript is lenient; write it as a function-typed property and strictFunctionTypes gets strict. That's not a bug, it's a deliberate carve-out.
14 min read
Every option explains itself — why the right one is right, and why each wrong one is wrong. Miss one and it comes back later on a spaced-repetition schedule.
This topic is about judgment and practical tradeoffs more than a single right answer — a mock interview is the better way to test yourself on it.
Be interviewed on this →Hints without spoilers, and a plain explanation of why a wrong answer was wrong — answered only from this lesson. Free account, no card.
Sign up freeWhat interviewers actually ask about this, and what a strong answer has to cover.
A strong answer covers
Likely follow-ups
strictFunctionTypes (one of the flags bundled into strict) makes function parameter types check contravariantly: a function expecting a narrower type isn't safely substitutable where a function expecting a wider type is required. That's the theoretically correct rule, and for most function-typed values, TypeScript enforces it strictly.
Except for one specific case: method shorthand syntax inside an interface or type — foo(x: Dog): void instead of foo: (x: Dog) => void. Method shorthand is checked bivariantly — more leniently, in both directions — even under strictFunctionTypes. Same signature, same meaning, genuinely different strictness, depending purely on which syntax you used to write it.
interface Animal { name: string }
interface Dog extends Animal { bark(): void }
interface Handler {
handle(a: Animal): void; // method shorthand — checked bivariantly
handleStrict: (a: Animal) => void; // function-typed property — checked contravariantly
}
const dogHandler: Handler = {
handle(d: Dog) {
d.bark();
},
handleStrict: (d: Dog) => { // ❌ error under strictFunctionTypes:
d.bark(); // Dog is narrower than Animal, not assignable here
},
};
handle accepts an implementation that only knows how to deal with Dog, even though the interface promises any Animal. handleStrict, with the exact same effective signature, gets rejected for doing the same thing. The difference is entirely about which of the two equivalent syntaxes was used to declare the member.
handle(a: Animal): void and handleStrict: (a: Animal) => void describe the exact same call signature. Only the shorthand form gets the bivariant check; the moment the identical signature is written as a property holding a function type, strictFunctionTypes applies in full.strictFunctionTypes is one of the flags bundled into "strict": true, covered elsewhere in this track, so this is a concrete example of what that specific flag actually changes. It's also a genuine carve-out from ordinary structural typing — the usual "does this shape have everything required" comparison gets deliberately relaxed for method shorthand specifically, and being able to explain why (not just that it happens) is a real signal of depth beyond "I've seen this error before."