skip to content

Callable and Constructable Types

Object types can describe things you call and things you `new`, not just data. Interviewers reach for call signatures, construct signatures, and overloads when they want to see you type a factory, a middleware, or a function that also carries properties.

part ofTypeScriptoverview, primer and where to startread it →
on this pageshow

explore

questions

13

In TypeScript, what does the type `(id: number) => Promise<User>` describe, and must the implementing function name its parameter `id`?

level: juniorimportance: must knowfreq 72%

answer

  1. a shape, not a value
  2. the arrow lives in type position
  3. positions matter, names do not
  4. return type sits after the arrow
  5. colon form belongs inside a member list

basics

~20 s

That type describes any function taking one number argument and returning a Promise of User. Parameter names inside a function type are labels for readers only — matching is positional, so an implementation may name the parameter anything.

solid answer

~50 s

`(id: number) => Promise<User>` is a **function type expression**: it says "a callable taking one `number` and returning `Promise<User>`". It is a type, not a value — the arrow here lives in type position and produces no code, unlike the arrow of an arrow function, which produces a real function. Parameters are matched by **position**, so `const load: (id: number) => Promise<User> = (userId) => fetchUser(userId)` is fine; the name `id` exists only for readability and for editor hints. Note the syntax detail: in this shorthand the return type follows `=>`, so writing `(id: number): Promise<User>` as a standalone type is a syntax error — the colon form is only legal as a call-signature member inside an object type or interface. Like everything in the type layer, the annotation is erased at compile time and checks nothing at runtime.

code

typescript · 12 lines
typescript
interface User {
  id: number;
  name: string;
}

type LoadUser = (id: number) => Promise<User>;

// Parameter renamed: matching is positional, so this is fine.
const load: LoadUser = async (userId) => ({ id: userId, name: 'Ada' });

// Nested function types; parentheses control what the arrow returns.
type MaybeFactory = (() => string) | number;

go deeper

for a junior

Be able to read (id: number) => Promise<User> aloud as "takes a number, returns a promise of User", and say plainly that the parameter name is a label matched by position, not by name.

for a middle

Explain that the arrow in type position produces no emitted code, that assignability compares parameters positionally, and that the return type binds as far right as possible so unions need parentheses.

for a senior

Show judgment about naming: positional matching means two same-typed parameters can be silently swapped, so argument names, ordering, and sometimes an options object are what actually prevent the bug the type cannot catch.

for a principal

Own the convention question — where the codebase declares reusable function types versus inlining them, and how that choice affects the blast radius when a callback signature changes across many call sites.

## What the shorthand says `(id: number) => Promise<User>` is a *function type expression*. It describes the shape of a callable: the parameter list on the left, the return type on the right. Any value with a compatible signature satisfies it; TypeScript is structural, so no declaration of intent is needed. ```ts type LoadUser = (id: number) => Promise<User>; const load: LoadUser = async (userId) => { return { id: userId, name: 'Ada' }; }; ``` The parameter of the arrow function needs no annotation because the annotation on `load` supplies it — the compiler contextually types `userId` as `number`. ## The arrow is in a different world The same `=>` token appears in two unrelated roles. In *value* position it builds a function: `(x) => x + 1` is code that exists after compilation. In *type* position it builds a type: `(x: number) => number` is a description that is deleted from the emitted JavaScript. Beginners routinely read a function type as an arrow function whose body is missing. It is not missing; there is no body in a type. This matters because everything else in the tree follows from it: nothing in a function type survives to runtime, so no argument is checked when the program actually runs. If the value came from `JSON.parse` or an untyped library, the annotation is a claim, not a guarantee. ## Parameter names are documentation Assignability compares parameters **by position**, never by name: ```ts type Handler = (event: string, at: number) => void; const h: Handler = (name, timestamp) => { console.log(name, timestamp); }; ``` This compiles. `name` is the `string`, `timestamp` is the `number`, because they sit in slots one and two. TypeScript has no named-argument calling convention, so the identifiers in a function type serve editors (signature help shows them) and human readers. Choose them well anyway — a type read as `(string, string) => void` tells the next person nothing, while `(userId: string, tenantId: string) => void` prevents a swapped-argument bug at review time. ## Where a function type can appear Anywhere a type is allowed: ```ts function retry(op: () => Promise<void>, times: number): Promise<void> { /* ... */ } interface Config { onError: (error: Error) => void; } type Middleware = (next: (value: string) => string) => (value: string) => string; ``` The last line shows two things worth noticing. A function type nests: a parameter can itself be a function type, and the return type can be a function type. And because the return type extends as far to the right as it can, `() => string | number` means "returns `string | number`", not "either a function or a number" — parenthesise as `(() => string) | number` when you mean the union of a callable and a number. That precedence rule is the source of a very common typo. ## The colon form is a different syntax Inside an object type or an interface, the same callable is written with a colon as a *call-signature member*: ```ts interface LoadUser { (id: number): Promise<User>; } ``` This describes exactly the same callable as the arrow shorthand. The member form exists because a member list can hold *more* members — which is how you describe something that is callable and also carries properties. As a standalone type, though, only the arrow form parses: `type Bad = (id: number): Promise<User>;` is a syntax error. ## What it does not do It does not create a function, does not check arguments at runtime, and does not enforce the parameter names. It also does not describe a constructor — `new`-ing a value typed with a plain call signature is an error, because constructability is written separately. And it does not make the value non-null: under `strictNullChecks` a variable of this type still cannot hold `undefined` unless you say so, but nothing stops an `any` value from being assigned into it and blowing up later. The practical takeaway for an interview: read a function type left to right as "takes these positions, gives back this", say plainly that the names are labels, and say that the whole annotation disappears from the output.

  • The `=>` in a function type and the `=>` in an arrow function look identical — what actually differs?
    Only the position. In value position `=>` builds a real function that exists in the emitted JavaScript. In type position it builds a description with no body and no output: `type F = (x: number) => string` emits nothing at all. Confusing the two leads people to look for a missing function body inside a type alias.
  • Does annotating a parameter as `(id: number) => Promise<User>` cause any runtime check when a caller passes something else?
    No. Types are erased during compilation, so the emitted JavaScript contains no check that the value is callable or that its argument is a number. If the value arrived from `JSON.parse`, a network response, or an untyped module, the annotation is a claim you have made, and a wrong claim surfaces as a runtime `TypeError`, not a compile error.

saying these in an interview costs you the question

  • Believes the implementation must reuse the parameter names from the type
  • Reads a function type as an arrow function whose body was omitted
  • Writes the return type after a colon in a standalone function type
  • Thinks the annotation checks arguments when the program runs
  • Assumes `() => string | number` means either a function or a number

context

open as a page

In TypeScript, a helper is supposed to receive the `Person` class itself, but `function make(ctor: Person)` rejects the argument `Person`. What does the type `Person` actually refer to, and how should the parameter be typed?

level: juniorimportance: must knowfreq 55%

basics

~20 s

A class declaration creates two separate things: a type named Person that describes instances, and a value named Person that is the constructor. A parameter receiving the class itself must be typed typeof Person, or new (name: string) => Person.

open as a page

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

level: middleimportance: must knowfreq 58%

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.

open as a page

In TypeScript, in what order does the compiler try the signatures in an overload list, and what goes wrong when a broader signature is written above a narrower one?

level: middleimportance: must knowfreq 55%

basics

~20 s

TypeScript reads an overload list top-down and commits to the first signature the arguments satisfy — not the most specific one. A broader signature listed above a narrower one therefore shadows it, and the narrower form's return type is never seen.

open as a page

In TypeScript, a function is written as two overload signatures followed by one implementation signature. How many functions does the emitted JavaScript contain, and which of those signatures can a caller use?

level: juniorimportance: should knowfreq 50%

basics

~20 s

The emitted JavaScript contains exactly one function — overload signatures are types and are erased. Callers may use only the overload signatures; the implementation signature is invisible to them and cannot be called on its own terms.

open as a page

In TypeScript, how do you type a value that is both callable and carries properties — for example a `log(message)` function that also has `log.level` and `log.reset()` — and why can't the `(message: string) => void` shorthand express it?

level: middleimportance: should knowfreq 46%

basics

~20 s

Declare the callable as a call-signature member of an object type or interface, alongside the properties. The arrow shorthand is one closed type expression with nowhere to put extra members; an intersection of a function type and an object type is the equivalent.

open as a page

In TypeScript, you need a second value — a test double or a wrapper — to have exactly the same type as an existing function called `parseConfig`. How do you express that without retyping its signature by hand?

level: middleimportance: should knowfreq 44%

basics

~10 s

Use a typeof type query: writing typeof parseConfig in type position yields the type the compiler already inferred for that value, including its parameters, its return type, and any properties hung on the function.

open as a page

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%

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.

open as a page

In TypeScript, the body of an overloaded function is type-checked against its implementation signature only. Given that, what does the compiler verify between the overload signatures and the implementation, and what does it not verify?

level: middleimportance: should knowfreq 45%

basics

~20 s

TypeScript checks only that each overload signature is compatible with the implementation signature, and that check is deliberately loose. It never verifies that the body returns the type a particular overload promised, so overloads are an unchecked promise.

open as a page

A TypeScript codebase annotates every callback parameter with the built-in `Function` type. What checking does that give up, and what should replace it?

level: seniorimportance: should knowfreq 36%

basics

~20 s

Function says only that a value is callable: any argument list type-checks and every call produces any. Replace it with an explicit call signature giving parameter and return types, or with a constraint such as (...args: never[]) => unknown where the function is never actually called.

open as a page

Given `interface Logger { (message: string): void; level: number }` in TypeScript, why does `const logger: Logger = (message) => {}` fail to compile, and how do you build a conforming value without an `as` assertion?

level: seniorimportance: should knowfreq 32%

basics

~20 s

An arrow function satisfies the call signature but has no level property, so the initializer is missing a member. Build the value with Object.assign, whose result type is the callable intersected with the properties, or assign properties onto a function declaration so the compiler folds them into its type.

open as a page

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?

level: seniorimportance: should knowfreq 40%

basics

~20 s

An 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.

open as a page

In TypeScript a function is overloaded as `(x: string): string` and `(x: number): number`. A caller holds a value typed `string | number` and passes it — why is that a compile error, and what are the options?

level: seniorimportance: should knowfreq 42%

basics

~20 s

Overload resolution commits to one signature, and neither accepts a union, so a string | number argument matches nothing and the compiler reports no matching overload. Fix it by narrowing at the call site, or drop overloads for a single union-parameter signature.

open as a page