Lesson 5 · Module 0.5 — Reading TS syntax
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.
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.
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.
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.
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 overload | function 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 properties | declare a real function that has a body |
| to the caller | identical — TS uses the first signature that fits, so order matters: put the specific one first | |
newPrefix 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.
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.”
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.
Recall, don’t re-read. (Answers reveal on click.)
{ description: string; (n: number): boolean }, the part (n: number): boolean is…new (s: string): Date sitting inside an interface?{ (x): Y } instead of the shorthand (x) => Y?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.
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.