skip to content

In TypeScript, what does the type `new (name: string) => Person` describe, and which values are actually assignable to it?

level: middleimportance: must knowfreq 58%

answer

  1. invoked with new, not called
  2. describes the maker, not the product
  3. member form uses new (...): T
  4. arrow functions have no such signature
  5. classes and class expressions match

basics

~20 s

It is a construct signature: it describes a value you invoke with new, taking a string and producing a Person. Classes and class expressions with a matching constructor are assignable; plain functions and arrow functions are not.

solid answer

~40 s

That is a **construct signature** — the type of something you call with `new`, not something you call directly. It says: invoke this with one string argument and you get back a `Person`. Written as a member of an object type or interface it reads `new (name: string): Person`, and an interface may hold several construct signatures, or mix them with ordinary call signatures — `StringConstructor` in the standard library does exactly that. Assignability is structural but not lenient here: a class declaration or class expression whose constructor matches is assignable, while a function type that has only a call signature is not, so an arrow function is rejected with "provides no match for the signature". An abstract class is also rejected, because its constructor type is an abstract constructor type.

code

typescript · 21 lines
typescript
class Person {
  name: string;
  constructor(name: string) {
    this.name = name;
  }
}

interface PersonCtor {
  new (name: string): Person;
}

const FromClass: PersonCtor = Person;
const FromExpression: PersonCtor = class extends Person {};
// const FromArrow: PersonCtor = (n: string) => new Person(n);
// Error: provides no match for the signature 'new (name: string): Person'

function spawn(ctor: PersonCtor, name: string): Person {
  return new ctor(name);
}

console.log(spawn(FromExpression, "Ada").name);

go deeper

for a junior

Recognise both spellings — new (name: string) => Person and the interface member new (name: string): Person — and know that the type after the colon is the instance you get back.

for a middle

Explain why a class is assignable and an arrow function is not, and show the interface-member form carrying statics alongside the construct signature. Be ready to write a factory parameter from memory.

for a senior

Argue when a construct-signature parameter is the right API at all: it commits callers to a class-shaped dependency. Show that you know it is erased, so an untrusted value can still blow up on new.

for a principal

Own the boundary decision — accepting constructors versus factory functions versus pre-built instances shapes testability, lazy initialisation and how much of your object graph a consumer must own.

## Two kinds of invocation, two kinds of signature An object type in TypeScript can carry several sorts of member: properties, index signatures, **call signatures** (`(x: number): string`) and **construct signatures** (`new (x: number): Foo`). The two signature kinds answer different questions — a call signature says what happens when you write `f(x)`, a construct signature says what happens when you write `new f(x)`. A type may have either, both, or several of each. ```ts interface PersonCtor { new (name: string): Person; // member form } type PersonCtorAlias = new (name: string) => Person; // standalone form ``` The two forms mean the same thing. The arrow form is convenient inline in a parameter list; the member form is what you use when the type also needs properties. ## What the return type means In `new (name: string): Person`, `Person` is the **instance** type produced, not the constructor. So the signature describes the maker; the annotation after the colon describes the product. That is the split the whole topic turns on: a factory parameter is typed with the construct signature, and whatever it returns is typed with the instance type. ## Which values satisfy it Assignability is structural, but a value only matches if its own type actually *has* a construct signature: ```ts class Person { name: string; constructor(name: string) { this.name = name; } } const A: new (name: string) => Person = Person; // ok: class declaration const B: new (name: string) => Person = class extends Person {}; // ok: class expression // const C: new (name: string) => Person = (n: string) => new Person(n); // error ``` The last line fails because an arrow function's type has a call signature and no construct signature — the compiler reports that the source "provides no match for the signature 'new (name: string): Person'". This is one of the few places where the type layer models a real runtime distinction faithfully: an arrow function genuinely cannot be constructed. A plain `function` declaration is likewise typed with only a call signature, so it too is rejected even though the underlying JavaScript value would be newable. Parameters are checked the usual way, so a constructor taking fewer parameters is assignable (extra parameters may be ignored), and the produced instance type must be assignable to the declared one, which means subclasses pass. ## Mixing call and construct signatures The standard library is the best real example. `lib.es5.d.ts` declares: ```ts interface StringConstructor { new (value?: any): String; (value?: any): string; readonly prototype: String; fromCharCode(...codes: number[]): string; } declare var String: StringConstructor; ``` One interface describes a value that behaves differently with and without `new`, and also carries statics such as `fromCharCode`. That is how the global `String` gets its type, and it is the pattern you copy when you need to describe an existing library object that is callable, constructable and has properties. ## Where you actually reach for it Any code that receives a class as a value: registries, plugin systems, dependency-injection containers, test doubles, `instanceof`-based lookups. Typing the parameter `new (…) => T` gives the caller a precise contract without naming a specific class, and gives the callee the right to write `new ctor(…)`. ```ts function spawnTwo(ctor: new (name: string) => Person): Person[] { return [new ctor("a"), new ctor("b")]; } ``` If you type the parameter `Function` instead, you can still call it but the result is `any`, and you have thrown away the very thing the annotation was for. ## And none of it exists at runtime A construct signature is a compile-time description. It disappears at emit; nothing checks at run time that the value you passed is really constructable. If the value arrives from `JSON.parse`, a dynamic `import`, or an `as` assertion, the compiler's promise is only as good as whatever produced it — the guarantee is on the code paths the checker actually saw.

  • Why is an arrow function rejected where a construct signature is expected?
    Its type has a call signature only, so it provides no match for the `new` signature. That mirrors the runtime: arrow functions are not constructable. A plain `function` declaration is also typed with just a call signature, so it is rejected too — even though the underlying value could be `new`ed. To describe a value usable both ways you declare an interface holding both signature kinds.
  • How would you describe a value that is callable, constructable and also has properties?
    Declare one interface with all three member kinds: a call signature, a construct signature, and ordinary properties. The standard library's `StringConstructor` does exactly this — `new (value?: any): String`, `(value?: any): string`, plus `fromCharCode`. That single type is what the global `String` is declared as.
  • What does typing a parameter as `Function` cost you compared with a construct signature?
    `Function` permits calling and constructing but the result is `any`, so every downstream use is unchecked, and it tells the caller nothing about arity or the produced type. The construct signature documents the contract and keeps the instance type flowing, which is usually the entire reason for the annotation.

saying these in an interview costs you the question

  • Says an arrow function is assignable to a construct signature
  • Reads the return type as the constructor rather than the instance
  • Thinks new (...) => T checks anything at runtime
  • Claims one interface cannot hold both call and construct signatures
  • Uses Function as the parameter type and loses the return type

context