skip to content

In TypeScript, why does passing an abstract class to a parameter typed `new () => Shape` fail to compile, and how do you type the parameter so it accepts one?

level: middleimportance: should knowfreq 38%

answer

  1. you cannot new an abstract class
  2. the signature carries that fact
  3. one direction of assignability only
  4. modifier goes before new
  5. abstract new () => Shape

basics

~20 s

An abstract class cannot be instantiated, so its constructor type is an abstract constructor type, which TypeScript refuses to assign to a plain new () => Shape. Type the parameter abstract new () => Shape, which accepts abstract and concrete classes alike.

solid answer

~40 s

A plain construct signature is a promise the callee may write `new ctor()`. An abstract class cannot be constructed, so the compiler classifies its type as an *abstract constructor type* and rejects the assignment with "Cannot assign an abstract constructor type to a non-abstract constructor type". If the callee genuinely never instantiates — it only stores the class, compares with `instanceof`, or extends it — write the parameter as `abstract new () => Shape`. That form accepts abstract and concrete classes both, since every concrete constructor type is assignable to the abstract one but not the reverse. The price is that inside the function you can no longer call `new` on it; if you need instantiation, keep the plain signature and require callers to pass a concrete subclass.

code

typescript · 21 lines
typescript
abstract class Shape {
  abstract area(): number;
}

class Unit extends Shape {
  area(): number {
    return 1;
  }
}

function build(ctor: new () => Shape): Shape {
  return new ctor();
}

let anyShapeCtor: abstract new () => Shape;
anyShapeCtor = Shape; // ok: the abstract form accepts an abstract class
anyShapeCtor = Unit; // ok: concrete constructors are assignable too

build(Unit);
// build(Shape);
// Error: Cannot assign an abstract constructor type to a non-abstract constructor type.

go deeper

for a junior

Know that an abstract class cannot be instantiated and that TypeScript therefore rejects it where a constructor is expected. Recognise the phrase "abstract constructor type" in the error.

for a middle

Explain the assignability direction — concrete constructors satisfy abstract new () => T, never the reverse — and pick the right signature based on whether your function calls new at all.

for a senior

Demonstrate the design read: the signature you publish tells callers whether instantiation is your job. Call out an as cast around this rule as a real hazard, since nothing checks abstractness at run time.

for a principal

Own the API-shape tradeoff across a codebase: requiring concrete constructors pushes wiring to callers, while accepting abstract ones keeps base classes usable as tokens and extension points. Decide which one your framework's contract should be.

## The rule the compiler is protecting `abstract class Shape { abstract area(): number; }` cannot be instantiated: `new Shape()` is an error, because the class has members with no implementation. A plain construct signature `new () => Shape` is exactly the licence to write `new ctor()`. If the compiler let you pass `Shape` there, the callee would be free to construct an object whose `area` does not exist — so the assignment is blocked at the boundary rather than at the call. The error names the two type kinds explicitly: "Cannot assign an abstract constructor type to a non-abstract constructor type." ## Abstract construct signatures TypeScript can describe the constructor of an abstract class with the `abstract` modifier on the signature itself: ```ts abstract class Shape { abstract area(): number; } class Unit extends Shape { area(): number { return 1; } } let ctor: abstract new () => Shape; ctor = Shape; // ok ctor = Unit; // ok — concrete constructors are assignable to the abstract form ``` The direction of assignability is the useful part: `abstract new () => T` is the *wider* type, accepting both kinds; `new () => T` is the narrower one, accepting only concrete classes. Choosing between them is therefore a statement about what your function does with the value: - **You call `new`** — use `new () => Shape`, and accept that abstract classes are correctly rejected. - **You never call `new`** — use `abstract new () => Shape` so callers may hand you a base class. A value typed `abstract new () => Shape` cannot be instantiated inside the function. That restriction is the whole point; if you find yourself asserting it back to a concrete signature with `as`, the parameter type was the wrong choice. ## What you do with a non-constructable constructor Plenty of useful code holds a class without building one: registries keyed by class, `instanceof` dispatch tables, metadata lookups, and — most commonly — anything that only means to `extends` the value. A base class you intend to derive from does not need to be concrete, and requiring it to be would be an artificial restriction. ```ts abstract class Shape { abstract area(): number; } function describe(list: Shape[], base: abstract new () => Shape): number { // no `new base()` here — only identity and instance checks return list.filter((s) => s instanceof (base as Function as new () => Shape)).length; } ``` That assertion in the last line is deliberately ugly; usually you would simply keep a concrete signature where you need `instanceof`, and reserve the abstract form for parameters you only pass along. ## The static side is still there Abstractness affects only constructability. The rest of the static side behaves normally: static members, `prototype`, and inherited statics are all visible through either signature form. `abstract` on a *member* is separate again — `abstract area(): number` says subclasses must supply it, and it lives on the instance side. ## Erasure, once more Nothing about this survives compilation. The emitted JavaScript for an abstract class is an ordinary class, and `new Shape()` would succeed at run time, producing an object whose `area` is simply missing. The abstract-constructor rule is a compile-time guard rail over a runtime that has no notion of abstractness at all — which is exactly why an `as` assertion around it is dangerous rather than merely noisy.

  • If both forms exist, why not just always use the abstract one?
    Because you lose the ability to write `new ctor()` inside the function. The plain form is a licence to construct; the abstract form is a promise not to. Choosing the widest type that still lets you do your job is right, and if your job is instantiation, the narrower signature is the correct contract.
  • What actually happens at run time if you cast an abstract class past this check and construct it?
    It constructs. Abstractness is erased, so the emitted class is ordinary JavaScript and `new` succeeds — producing an object missing whatever the abstract members promised. The first call to that member throws a TypeError far from the cast, which is why the assertion is a genuine hazard rather than a formality.
  • Does abstractness affect access to the class's static members?
    No. It restricts constructability only. Static properties and methods, inherited statics and `prototype` are all reachable through either signature form, so a registry that reads a static id from an abstract base works fine.

saying these in an interview costs you the question

  • Says the abstract class only needs a cast to work
  • Believes abstract new () => T can still be instantiated inside the function
  • Thinks the restriction is enforced at runtime
  • Claims concrete constructors are rejected by an abstract construct signature
  • Confuses an abstract member with an abstract constructor type

context