Hi everyone,
Yesterday I spent some time learning more about TypeScript, and I came
across the term "call signature." It's quite helpful when you want
to define something like a logger middleware or understand how libraries
such as dayjs declare their typings.
Source: https://www.typescriptlang.org/docs/handbook/2/functions.html#call-signatures
First, understand that a function is an object
You should know that the typeof a function prototype is object ---
I'm referring to the prototype, not typeof function itself.
If you are not familiar with prototypes, I strongly suggest reading
about them first before continuing (https://developer.mozilla.org/en-US/docs/Learn_web_development/Extensions/Advanced_JavaScript_objects/Object_prototypes).
The problem we want to solve
How can we configure a function and then execute it with those
configurations?
This is somewhat similar to a closure, but closures encapsulate
state using variable scope. With a call signature, the state is exposed
outside --- meaning we can mutate the properties if needed.
You might encounter this pattern somewhere in your codebase, especially
in libraries like dayjs or when implementing a logger middleware.
But how do we define it properly in TypeScript?
Call Signatures in TypeScript
In JavaScript, functions can have properties in addition to being
callable.
However, the typical function type syntax does not allow declaring
properties. If we want to describe something callable with
properties, we can use a call signature inside an object type.
Before continuing, it's helpful if you already understand closures or
have used them to encapsulate function state (for example, in React
hooks).
A call signature is similar to a closure --- but the key difference is
that the state lives outside the function, so it can be modified.
Let's look at an example.
Example 1 --- Mini Day.js Clone
type DayJS = {
timezone: string;
description: string;
(date: string | number | Date): string;
};
const dayjs = function (date: string | number | Date) {
const tz = dayjs.timezone;
const formatter = new Intl.DateTimeFormat("en-US", {
timeZone: tz,
year: "numeric",
month: "2-digit",
day: "2-digit",
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
});
return formatter.format(new Date(date));
} as DayJS;
dayjs.timezone = "Asia/Ho_Chi_Minh";
dayjs.description = "Mini DayJS clone";
console.log(dayjs("2026-02-04"));
// 02/04/2026, 07:00:00 AM
π Here, dayjs is both:
- callable like a function\
- configurable via properties
This is exactly what a call signature enables.
Example 2 --- Logger Middleware
Now let's implement a logger middleware with configurable properties
such as:
-
isEnabled(based on production env) - log type (
warning,error,info) - message payload
type LoggerData = { message: string; metadata?: unknown };
type LoggerType = "warning" | "error" | "info";
type LoggerMiddleware = {
isEnabled: boolean;
type: LoggerType;
(data: LoggerData): void;
};
const initializedSomeService = () => {
const warningLogger: LoggerMiddleware = function (data) {
if (!warningLogger.isEnabled) return;
console.warn(`TYPE:${warningLogger.type}`, { data });
};
// configure properties
warningLogger.isEnabled = process.env.NODE_ENV === "production";
warningLogger.type = "warning";
// execute like a function
warningLogger({
message: "This is a warning message",
metadata: { id: "ab85592d-b7cf-4623-9884-aa70f40814f7" },
});
};
initializedSomeService();
Result:
TYPE:warning {
data: {
message: 'This is a warning message',
metadata: { id: 'ab85592d-b7cf-4623-9884-aa70f40814f7' }
}
}
When should you use call signatures?
Use them when you want something that is:
β
callable like a function\
β
configurable via properties\
β
strongly typed
Typical real-world use cases:
- logger utilities\
- analytics trackers\
- feature flag executors\
- SDK-style APIs\
- libraries like dayjs
Closure vs Call Signature (Quick Insight)
Closure Call Signature
Encapsulates state Exposes state
Safer from mutation Mutable
More functional style More library-style
Common in hooks Common in SDK design
Neither is better --- choose based on your design goal.
If you want immutability β prefer closures.\
If you want configurability β call signatures shine.
Final Thoughts
Call signatures are a small TypeScript feature, but once you understand
them, you'll start noticing this pattern in many libraries.
They provide a powerful way to design APIs that feel natural to
JavaScript --- configurable, callable, and strongly typed.
Definitely worth adding to your TypeScript toolbox π
Top comments (0)