Hi everyone, I'm Kelvyn. Yesterday I did a deep dive into the
constructor signature side, and today we'll go through this topic to
clarify some common questions:
- What is the diff between
new(constructor signature) vs executing
a function (call signature)?\ - How can we declare their typings in TypeScript?\
- How can we implement a function that supports both call and
constructor signatures?
Great! Let's start 🚀
Prerequisite
If you still haven't read about call signatures, I recommend reviewing
it first:
👉 https://dev.to/kelvynthai/build-a-simple-mini-dayjs-clone-logger-middleware-by-call-signatures-in-typescript-8pj
The Problem
Sometimes you might see code like this in your codebase:
Boolean();
new Number(1);
These are built-in objects in JavaScript supported by the runtime
environment.
But what is really happening behind the scenes?\
Why can some APIs be called both with and without new?\
And more importantly --- how do we define their typings in TypeScript?
1. Constructor Signature vs Call Signature
Constructor Signature
A constructor must be invoked with the new keyword.
new Number(1);
When using new, JavaScript:
- Creates a new object\
- Links it to the constructor's prototype\
- Binds
thisto that object\ - Returns the object
👉 Important rule: Constructors should return objects, not
primitives.
Call Signature
A call signature means invoking a function directly:
Number(1);
Typically, call signatures return primitive values such as string,
number, or boolean.
So the mental model becomes very simple:
Constructor → Object\
Call → Primitive
2. Declaring Both Signatures in TypeScript
TypeScript allows us to define both call and constructor signatures
inside a single type.
type MyDate = {
new (dateStr: string): String; // constructor -> object
(dateNum?: number): string; // call -> primitive
};
Now let's implement it with pure logic --- no built-in Date usage --- so
we can focus entirely on typing behavior.
3. Implementing Pure Logic
type MyDate = {
// eslint-disable-next-line
new (dateStr: string): String; // constructor -> object
(dateNum?: number): string; // call -> primitive
};
const MyDate: MyDate = function (this: unknown, d?: string | number) {
if (new.target) {
// always ensure constructor returns an object
return new String(`PARSED_DATE:${d}`);
}
return !!d ? `TIMESTAMP:${d}` : "DEFAULT_DATE";
} as MyDate;
const dateWithNew = new MyDate("Sat 7 Feb 2026");
// String object
const dateWithoutNew = MyDate(123456);
// primitive string
console.log(dateWithNew, typeof dateWithNew); // [String: 'PARSED_DATE:Sat 7 Feb 2026'] object
console.log(dateWithoutNew, typeof dateWithoutNew); // TIMESTAMP:123456 string
console.log(new Date(), typeof new Date()); // 2026-02-07T13:58:57.212Z object
What Happens Here?
✅ new MyDate("...") returns a String object\
✅ MyDate(123) returns a primitive string
We clearly demonstrate the hidden JavaScript behavior:
Anything called with
newshould behave like an object instance.
Senior Tip ⚠️
Avoid using String, Number, or Boolean constructors in real
production code because they create wrapper objects.
typeof "hello" // string
typeof new String("hello") // object ❗
This can silently break comparisons:
"hello" === new String("hello") // false
👉 But for learning constructor vs call signatures, this is a
perfect example.
Conclusion
Understanding the difference between constructor and call signatures
helps you:
✅ Read built-in APIs more confidently\
✅ Design flexible libraries\
✅ Improve your TypeScript modeling skills\
✅ Understand how JavaScript really works under the hood
If you remember only one thing from this article, let it be this:
Constructor → Object\
Call → Primitive
Once you internalize that rule, many "weird" JavaScript behaviors
suddenly make sense.
Happy coding 🚀
SOCIAL SHARE CARD GENERATOR