Lesson 5 · Module 0.5 — Reading TS syntax

Call & construct signatures — the object-type form of a function

You already read (n: number) => string as “a function.” There is a second way to write that same type — { (n: number): string } — and that longer form is what lets a function type also carry properties, be called several ways, or be used with new. Learn to read both and the strange-looking members inside interfaces stop being strange.

Two ways to write a function type

The arrow form from Lesson 0.1 is just shorthand. Spelled out inside braces, the same function type becomes a call signature — note the : before the return type, where the arrow form used =>:

type Greet  = (name: string) => string;     // shorthand arrow form
type Greet2 = { (name: string): string };   // call-signature form — identical meaning

The cue to read: inside an object type, a member that is just ( params ): Return with no property name in front is a call signature. It says “a value of this type is callable like that.” So Greet and Greet2 are the same type written two ways.

Why bother with the long form? A function plus properties

The arrow shorthand can only describe “a function and nothing else.” The braces let you bolt regular properties onto the callable — a shape often called a hybrid type:

type DescribableFunction = {
  description: string;          // a normal property
  (someArg: number): boolean;   // ← call signature: the value is also callable
};

function doSomething(fn: DescribableFunction) {
  console.log(fn.description + " returned " + fn(6));  // read .description AND call fn()
}

Read DescribableFunction as: “a function you call with a number to get a boolean, and which also has a .description string.” You meet this all the time in the wild — a callback that carries a .displayName, a middleware with attached config, React’s fn.defaultProps.

Several call signatures = overloads

Put more than one call signature in the braces and you’ve described a function that can be called in more than one way (an overload). Read them top-to-bottom as “or”:

type Parse = {
  (input: string): string[];   // call it with a string → get string[]
  (input: number): number[];   // …or call it with a number → get number[]
};

So Parse means “call with a string and you get string[]; call with a number and you get number[].” The shorthand arrow form simply can’t express this — another reason the braced form exists.

Using an overload: the one signature you don’t see

Two call signatures describe the type, but a real function has only one body. So an implementation must give a single signature broad enough to cover both listed forms, then split the cases apart at runtime. Assigning a function straight to Parse looks like this:

const parse: Parse = (input: string | number): any => {
  return typeof input === "string"
    ? input.split(",")   // string branch → string[]
    : [input];           // number branch → number[]
};

parse("1,2,3");   // string[]  ← the caller sees only the two Parse signatures…
parse(42);        // number[]  ← …never the broad (string | number) => any below

Read two things carefully. First, the implementation’s own signature (string | number => any) is invisible to callers — they only ever see the two call signatures from Parse. Second, that loose any return is a deliberate escape hatch: TypeScript does not verify that “string in ⇒ string[] out” really holds inside the body, so overloads are a place where unsafety can hide — worth knowing when you read a library’s types.

The same overload set is more often written as bare function headers — your two call signatures, plus one hidden implementation:

function parse(input: string): string[];                        // overload 1 — callers see this
function parse(input: number): number[];                        // overload 2 — and this
function parse(input: string | number): string[] | number[] {  // implementation — hidden
  return typeof input === "string" ? input.split(",") : [input];
}

These are two spellings of the same feature — an overloaded function. The only real difference is where you reach for each:

call-signature overloadfunction overload
written as{ (a): X; (a): Y }function f(a): X;
function f(a): Y;
function f(a) { … }
reach for it to…type a value — a parameter, or a callable that also has propertiesdeclare a real function that has a body
to the calleridentical — TS uses the first signature that fits, so order matters: put the specific one first

Construct signatures: the same idea, with new

Prefix a signature with new and it describes something you call with new — a construct signature. An interface can hold both, describing a value that works as a plain function and as a constructor. A classic example:

interface CallOrConstructor {
  new (s: string): Date;   // construct signature → used with `new`
  (n?: number): number;     // call signature      → used as a plain call
}

function fn(date: CallOrConstructor) {
  let d = new date('2021-12-20');   // matches the construct signature → Date
  let n = date(100);                // matches the call signature      → number
}

Read new (s: string): Date as “you can new this with a string and get a Date,” and the bare (n?: number): number as “you can also just call it.” This is exactly how TypeScript’s own lib.d.ts types globals like DateConstructor — which is why Date('x') and new Date('x') are both legal.

Reading vs building

You rarely need to build such a value yourself (it takes some fiddly JavaScript). For reading, just spot the two forms: ( ): T = “you can call it,” and new ( ): T = “you can use it with new.”

Read it this way

Inside an object/interface type: an unnamed ( … ): T member is a call signature (“this is callable”). Put new in front and it’s a construct signature (“this is newable”). A named member like description: string is just an ordinary property. They mix freely in one type.

Check yourself

Recall, don’t re-read. (Answers reveal on click.)

Inside { description: string; (n: number): boolean }, the part (n: number): boolean is…
How do you read new (s: string): Date sitting inside an interface?
Why write a function type as { (x): Y } instead of the shorthand (x) => Y?

Primary source

The handbook covers both forms on one page: More on Functions — Call Signatures and Construct Signatures (hybrid types appear under Object Types). Best way to feel it: paste DescribableFunction and CallOrConstructor into the TS Playground and hover the calls.

Ask your teacher

See a bracketed type with a nameless ( ): T line and aren’t sure what it means? Paste it in and ask — naming each member (property / call signature / construct signature) is exactly the reading muscle this lesson builds.