In TypeScript, a `class User {}` declaration puts two different things into scope under the same name. What are they, and what does `typeof User` mean when written in a type position?
answer
- one declaration, two meanings
- value space and type space
- instance side versus class side
- typeof queries the class value
- InstanceType recovers what new produces
basics
~20 sA class declaration creates a value — the constructor object, holding the static members — and a type of the same name meaning an instance. In a type position, typeof User refers to that class value itself; InstanceType<typeof User> gets back the instance type.
solid answer
~50 sA class declaration is unusual in that it introduces a name in both spaces at once. In value space, `User` is the class object itself: the thing you call with `new`, and the thing the static members hang off. In type space, `User` means *an instance* — `const u: User` is one object produced by the class, not the class. When you need to talk about the class value in a type position you write `typeof User`, which is a type query on the value and gives you the class side, statics included. That is the annotation for a parameter that receives the class rather than an instance, as in a registry or a factory. Going the other way, `InstanceType<typeof User>` recovers the instance type, which matters when the class is anonymous or you only have hold of the constructor type.
code
typescript · 21 linesclass User {
name = "anon";
static create(): User {
return new User();
}
}
// `User` in a type position means an instance
const u: User = new User();
// `typeof User` is the class value itself, statics included
function build(ctor: typeof User): User {
return ctor.create();
}
build(User);
// InstanceType goes back from the class side to the instance side
type SameAsUser = InstanceType<typeof User>;
const v: SameAsUser = u;
console.log(v.name);go deeper
Know that after class User {} you can both call new User() and annotate const u: User, and that the annotation means one instance rather than the class itself.
Explain the two declaration spaces and show the mechanics: typeof User is a type query naming the class value with its statics, and InstanceType<typeof User> converts back to the instance type.
Use this to type factories, registries and containers correctly, and diagnose "type 'typeof X' is not assignable to type 'X'" instantly as a class-versus-instance mix-up rather than a structural mismatch.
Own the API shape: deciding whether a module hands out classes, instances, or plain factory functions determines how much of the class side leaks into your public types and how hard the surface is to mock or replace later.
## Two declaration spaces TypeScript keeps names for values and names for types in separate spaces. Most declarations occupy exactly one: `const x = 1` is a value only, `interface Shape {}` and `type Id = string` are types only. A `class` is one of the few things that declares in **both** at once, and almost every confusing error in this area comes from reading the wrong space. ```ts class User { name = "anon"; static create(): User { return new User(); } } const a = User; // value position: the class object itself const b: User = new User(); // type position: one instance ``` - **Value `User`** is the constructor: callable with `new`, carrying the static members (`User.create`), assignable to a variable, passable as an argument. - **Type `User`** is the *instance* type: the members declared on the class body without `static`. These are not the same thing and are not interchangeable. `const c: User = User;` is an error — the class object is not an instance of itself. ## typeof in a type position Writing `typeof` inside a type is a **type query**, not the runtime `typeof` operator that produces strings like `"function"`. It means "the type of that value". Applied to a class name, it yields the class side: ```ts function build(ctor: typeof User): User { return new ctor(); } build(User); // ok — you pass the class, not an instance // build(new User()); // error — an instance is not the class ``` `typeof User` includes the static members, so a function taking `typeof User` can call `ctor.create()`. This is exactly what you want in a registry, a plugin table, a factory, or a DI container: the parameter holds classes and instantiates them later. The pattern generalises to any value: `typeof someConfig` gives the type of that object without you writing it out. On classes it just happens to be the only way to name the class side, since the plain name is taken by the instance type. ## InstanceType: going back the other way `InstanceType<T>` is a built-in utility that takes a constructor type and yields what `new` on it produces. For a named class it is redundant — `InstanceType<typeof User>` is simply `User` — but it earns its keep whenever the class does not have a convenient name: ```ts const Anon = class { id = 1 }; type AnonInstance = InstanceType<typeof Anon>; // { id: number } ``` A class expression assigned to a `const` gives you a *value* but no instance type name at all — `Anon` in a type position does not exist. `InstanceType<typeof Anon>` is how you name what it builds. The same applies to a factory that returns a class, or to a generic helper whose type parameter is the constructor. ## Why interviewers ask this Three very common errors all reduce to mixing up the two spaces: 1. Annotating a factory parameter as `User` when it should be `typeof User`, then being confused that passing the class fails. 2. Trying `InstanceType<User>` — that fails, because `InstanceType` expects a constructor type and `User` is already the instance type. 3. Expecting an `implements` clause to be about the class value. It is not: the conformance check compares the **instance** type against the named contract, which is why static members are outside its reach entirely. There is also a nice symmetry with erasure. The instance type is a pure compile-time construct — it vanishes. The class value is real JavaScript that survives compilation. The one name refers to something erased and something emitted, depending on where you write it, which is a compact demonstration of the whole compile-time/run-time boundary that TypeScript sits on. ## Reading errors with this in mind When a message says "'User' only refers to a type, but is being used as a value here", you have written a type-space-only name (an interface, a type alias) where a value was needed. When it says "only refers to a value", the reverse. Classes never produce either message, precisely because they fill both spaces — but they do produce the subtler mismatch: "Type 'typeof User' is not assignable to type 'User'", which is the compiler telling you that you handed it the class where it wanted one of the things the class makes.
- Why does `InstanceType<User>` fail while `InstanceType<typeof User>` works?`InstanceType` is constrained to constructor types — it needs something you can call with `new`. In a type position `User` is already the instance type, which has no construct signature, so it fails the constraint. `typeof User` is the class value's type, which does have one, so the utility can extract what it produces.
- How would you type a registry that stores classes and instantiates them on demand?Store the class side, not instances: a map whose values are `typeof SomeClass`. Then `new entry()` produces the instance, and `InstanceType<typeof entry>` names its type if you need to annotate the result. Annotating the map with the instance type instead is the classic mistake — it then accepts already-built objects and rejects the classes themselves.
- Does an `implements` clause check the class's static members?No — the conformance check compares the class's *instance* type against the named contract. Anything on the class side, including statics, is simply outside what the clause looks at. That is a direct consequence of the two-space split: the clause lives on the instance half of the declaration.
saying these in an interview costs you the question
- Thinks `User` in a type position means the class itself
- Reads `typeof` in a type position as the runtime operator returning a string
- Writes `InstanceType<User>` instead of `InstanceType<typeof User>`
- Says a class declares only a type, like an interface
- Assumes passing a class where an instance type is expected will work