In TypeScript, given `interface Checker { check(name: string): boolean }` and `class NameChecker implements Checker { check(s) { return s.length > 0; } }`, why does the compiler complain about `s` when `noImplicitAny` is on?
answer
- the clause only checks, it never informs
- types flow out of the class, not in
- contextual typing needs an annotated target
- implicit any on the method parameter
- any is assignable, so the check passes
basics
~20 sAn implements clause never supplies types. The class type is built from the class body first and only then compared to the interface, so the unannotated parameter is an implicit any — an error under noImplicitAny. Annotate it yourself.
solid answer
~40 sBecause `implements` is only a check, never a source of types. The compiler builds `NameChecker`'s type from what the class body actually declares, and *then* asks whether that type is assignable to `Checker`. Nothing flows in the other direction, so `s` has no contextual type, becomes an implicit `any`, and `noImplicitAny` reports it. The assignability check itself still passes, because `any` is assignable to `string` — so with the flag off you silently get an untyped parameter and `s.length` on a number would go unnoticed. Contextual typing does work when a value is checked *against* an annotated target, such as an object literal assigned to a variable typed `Checker`; a class body is not that. The fix is to annotate the parameter explicitly, which is the price of the class form.
code
typescript · 19 linesinterface Checker {
check(name: string): boolean;
}
// Object literal: the annotation is the contextual target, so s: string
const literalChecker: Checker = {
check(s) {
return s.length > 0;
},
};
// Class: no contextual typing from the clause, so annotate it yourself
class NameChecker implements Checker {
check(s: string): boolean {
return s.length > 0;
}
}
console.log(literalChecker.check("a"), new NameChecker().check("b"));go deeper
Know that you must write the parameter and return types on class members yourself, and that an unannotated parameter becomes an implicit any which strict settings will reject.
Explain the direction of the check: the class type is constructed from the body first and only then tested for assignability, so nothing flows back in, unlike an object literal assigned to an annotated variable.
Point out that the conformance check still passes with an implicit any, so the strict flag is the real safety net here, and be ready to argue for a typed literal or factory when the class buys you nothing.
Treat this as evidence for how much a codebase leans on strictness settings rather than on clauses that look protective: decide which guarantees are actually enforced, and standardise the pattern teams use for contract-shaped objects.
## The surprising part Everyone expects that once you write `implements Checker`, the compiler knows `check`'s parameter is a `string`. It does not, and this is called out in the TypeScript documentation as a common source of error. Under `noImplicitAny` (which `strict` turns on) you get an error on the parameter; with the flag off you get a silently untyped parameter, which is worse. ## Why: the direction of the check TypeScript builds the class's type from its **own declarations** — fields, methods, accessors, their annotations and their inferred initializers. Only after that type exists does it ask a single yes/no question: *is this type assignable to `Checker`?* Information flows one way, from the class to the check. The clause is a test, not an input. Contextual typing — the mechanism that fills in unannotated parameters — needs the opposite arrangement: an expression being checked *against* a type that is already known. ```ts interface Checker { check(name: string): boolean; } // Contextual typing works: the annotation is the target const c: Checker = { check(s) { return s.length > 0; } // s: string, inferred }; // No contextual typing: the class body is not checked against a target class NameChecker implements Checker { check(s) { return s.length > 0; } // s: any -> error under noImplicitAny } ``` The object literal is an *expression* whose expected type is known before it is checked, so the parameter gets a contextual type. A class declaration is not an expression being assigned to anything; its members are declarations that stand on their own. ## The check still passes A subtle point worth saying out loud in an interview: the `implements` check does **not** fail here. A parameter of type `any` is assignable in both directions, so `(s: any) => boolean` satisfies `(name: string) => boolean`. That means the *only* thing standing between you and an untyped method is `noImplicitAny`. Turn strictness off, and the class compiles clean while `s.length` would happily accept a number, a `Date`, or `null`. The same applies in reverse: if you annotate the parameter *wrongly*, say `check(s: number)`, then the implements check does fire and you get an error on the class — but the message points at the class heading, not at the method, which is why people sometimes miss which member is at fault in a large class. ## The arity gap Related, and often asked as the immediate follow-up: a method may declare **fewer** parameters than the contract and still satisfy it. ```ts class Lazy implements Checker { check() { return true; } // no parameters at all — accepted } ``` This is not a bug in `implements`; it is how function assignability works generally — a callback that ignores arguments is usable wherever one taking more arguments is expected. But combined with the no-inference rule it means `implements` is a much looser guarantee than people assume: it will not tell you that you forgot a parameter, only that the ones you declared are incompatible. ## What to do about it 1. **Annotate the members.** In a class you write the types out. That is the accepted cost of the class form, and it keeps the method readable on its own. 2. **Keep `noImplicitAny` on** (it comes with `strict`). It is the only thing that catches the omission, since the conformance check will not. 3. **Prefer a typed factory or object literal when the shape is all you need.** `const checker: Checker = { check(s) { … } }` gets full contextual typing, no repetition, and no class. 4. **Do not remove the `implements` clause** when it fails to help with inference — it still catches the case where you spell a member wrong or change its return type, which is its actual job. ## The mental model to keep Everything unexpected about `implements` comes from one rule: it is a *predicate on the class*, evaluated after the class type exists. It cannot add members, cannot supply parameter types, and cannot make a missing method appear — those would all require it to run before or during the construction of the class type, and it does not. Say that rule in an interview and every gap follows from it.
- Would the class still satisfy the interface if you left the parameter as an implicit `any`?Yes. `any` is assignable in both directions, so `(s: any) => boolean` matches `(name: string) => boolean` and the conformance check passes. The only diagnostic you get is the implicit-any error itself, so with that check disabled the class compiles clean while the method is effectively untyped.
- Where in the code does the error appear if you annotate the parameter with the wrong type?On the class declaration, not on the method. The compiler reports that the class incorrectly implements the interface and then explains which property is incompatible. In a large class that indirection is why people hunt for the wrong member — read the sub-message naming the property before scanning the body.
- Does declaring fewer parameters than the interface break the implements check?No. A function that ignores trailing arguments is assignable to one that declares them, so `check()` with no parameters satisfies `check(name: string): boolean` as long as the return type matches. It is worth knowing because it means the clause will not catch a parameter you forgot — only one you typed incompatibly.
saying these in an interview costs you the question
- Assumes the implements clause contextually types method parameters
- Thinks the compiler infers member types from the interface
- Believes the implicit-any error means the class fails the conformance check
- Says annotating is redundant because the interface already says it
- Claims a missing parameter would be caught by implements