In TypeScript, you want every class implementing your `Codec` interface to also expose a static `fromJSON` method. Why does putting it in the interface and writing `class Doc implements Codec` fail to enforce that, and what does enforce it?
answer
- one clause, one side
- statics sit on the constructor
- typeof Doc is the other type
- no static implements exists
- annotate the class value, or satisfies
basics
~20 sAn implements clause checks only the instance side, so static members and construct signatures written in the interface are never enforced on the class. Declare a separate static-side interface and check the class value against it, with an annotated const or a satisfies expression.
solid answer
~40 s`implements` compares the class's **instance** type against the interface. Statics and construct signatures live on the other side of the class — the constructor object, typed `typeof Doc` — and `implements` never looks there. Put a construct signature in the interface and the clause actually gets *worse*: you get "Class 'Doc' incorrectly implements interface 'Codec'. Type 'Doc' provides no match for the signature 'new (raw: string): Codec'", because it is checking whether *instances* are constructable. The fix is to split the contract in two: an instance interface used with `implements`, and a static-side interface holding the construct signature and the statics, checked against the class value itself — `const _: CodecStatic = Doc;` or `Doc satisfies CodecStatic`. There is no `static implements` clause in the language.
code
typescript · 30 linesinterface Codec {
encode(): string;
}
interface CodecStatic {
new (raw: string): Codec;
fromJSON(json: string): Codec;
}
class Doc implements Codec {
raw: string;
constructor(raw: string) {
this.raw = raw;
}
encode(): string {
return this.raw;
}
static fromJSON(json: string): Doc {
return new Doc(String(JSON.parse(json)));
}
}
// `implements` checked only encode(); this line checks the static side
const DocStatic: CodecStatic = Doc;
function restore(ctor: CodecStatic, json: string): Codec {
return ctor.fromJSON(json);
}
console.log(restore(DocStatic, '"hello"').encode());go deeper
Know that static members belong to the class itself, not to instances, and that an implements clause only checks instance members. Recognise the "Property is missing in type" error when a static was expected.
Explain the split: Doc is the instance type, typeof Doc the static side with the construct signature. Show how a static-side interface plus an annotated const or satisfies performs the check implements cannot.
Show judgment about where the check lives — at the class definition, or at every call site that consumes the class — and account for inherited statics and generic classes whose statics cannot reference type parameters.
Own the contract design: requiring a static factory across an open set of implementations couples every implementer to a construction protocol you cannot verify at run time. Weigh it against a registry of plain factory functions.
## Two sides, one class Every class declaration produces two types. The **instance side** is the type named by the class — its fields and methods. The **static side** is the type of the class *value*, written `typeof Doc` — its static members, its `prototype`, and a construct signature describing `new Doc(...)`. An `implements` clause is a check on the instance side only. It is a convenience assertion — it changes nothing about the class's own type, and it never widens or narrows inference — and it compares exactly one of the two sides. ## What goes wrong when you put statics in the interface ```ts interface Codec { encode(): string; fromJSON(json: string): Codec; // author intended a static } class Doc implements Codec { encode(): string { return ""; } static fromJSON(json: string): Doc { return new Doc(); } } // Error: Class 'Doc' incorrectly implements interface 'Codec'. // Property 'fromJSON' is missing in type 'Doc'. ``` The compiler is right and the author is surprised: `fromJSON` exists, just not on instances. Adding a construct signature to the interface fails the same way, with the characteristic wording "provides no match for the signature 'new (raw: string): Codec'" — the clause asks whether an *instance* is constructable, which of course it is not. ## The two-interface pattern Split the contract along the same seam the class has: ```ts interface Codec { encode(): string; } interface CodecStatic { new (raw: string): Codec; fromJSON(json: string): Codec; } class Doc implements Codec { raw: string; constructor(raw: string) { this.raw = raw; } encode(): string { return this.raw; } static fromJSON(json: string): Doc { return new Doc(String(JSON.parse(json))); } } const DocStatic: CodecStatic = Doc; // the static side IS checked here ``` The annotated `const` is what does the enforcing: it forces `typeof Doc` to be assignable to `CodecStatic`, which checks the construct signature *and* the statics in one go. `Doc satisfies CodecStatic` does the same check without introducing a second binding of a widened type, and is usually the cleaner spelling when you only want the assertion. The same trick appears wherever a function receives the class: a parameter typed `CodecStatic` checks both sides at the call site, so a class missing `fromJSON` is rejected there rather than at its declaration. ```ts function restore(ctor: CodecStatic, json: string): Codec { return ctor.fromJSON(json); } ``` Note what this buys you over `implements`: the callee can both construct instances and reach the statics through the same value. ## Why the language works this way There is no `static implements` clause. TypeScript's structural model has no notion of "a type that a constructor must conform to" attached to a class declaration, because a class's static side is just an ordinary object type and can be checked like any other value. Rather than add a second clause, the language leaves you to assert assignability wherever you like. Once you see the class value as a value with a type, the pattern stops feeling like a workaround. ## Practical consequences - The check is only as good as where you put it. An annotated `const` in the defining module fails the build at the definition; a parameter type fails only on call sites you actually have. - Statics are inherited, so a subclass satisfies the static interface via its base, which is usually what you want for a shared `fromJSON`. - Generic statics do not survive this well — a static member cannot reference the class's own type parameters, so a `fromJSON` on a generic class returns the unparameterised instance type. - And as always, nothing is verified at run time. `CodecStatic` is erased; if a class arrives from a dynamic import or through an `as` assertion, `ctor.fromJSON` may simply be `undefined`.
- What is the practical difference between `const _: CodecStatic = Doc` and `Doc satisfies CodecStatic`?Both check that `typeof Doc` is assignable to `CodecStatic`. The annotated const also creates a binding whose type is the *widened* `CodecStatic`, so using it loses `Doc`'s own specifics; `satisfies` performs the check and keeps the original type. When you only want enforcement at the definition site, `satisfies` is the cleaner spelling.
- Why does adding a construct signature to the interface you `implements` produce such a confusing error?The clause checks the instance type against the interface, so it is asking whether an *instance* of the class can be called with `new`. It cannot, so you get "provides no match for the signature". The construct signature was meant to describe the class value, which `implements` never examines.
- Does a subclass automatically satisfy the static-side interface its base satisfies?Yes, because static members are inherited through the class heritage chain, so a subclass's static side includes the base's statics. The construct signature is checked against the subclass's own constructor, though, so a subclass that changes constructor parameters can break the assignment even when the statics are fine.
saying these in an interview costs you the question
- Says implements checks both instance and static members
- Puts a construct signature in the interface a class implements
- Thinks a static method can satisfy an instance-side requirement
- Claims TypeScript has a static implements clause
- Believes the static-side check survives to run time