Question
TypeScript Constructor Overloading: How to Use Overload Signatures Correctly
Question
I want to use constructor overloading in TypeScript. The language specification mentions constructor overloads, but I could not find a clear example.
I am experimenting with a simple class like this:
interface IBox {
x: number;
y: number;
height: number;
width: number;
}
class Box {
public x: number;
public y: number;
public height: number;
public width: number;
constructor(obj: IBox) {
this.x = obj.x;
this.y = obj.y;
this.height = obj.height;
this.width = obj.width;
}
constructor() {
this.x = 0;
this.y = 0;
this.width = 0;
this.height = 0;
}
}
When I run tsc BoxSample.ts, TypeScript reports a duplicate constructor definition. How should constructor overloading be written correctly in TypeScript?
Short Answer
By the end of this page, you will understand how constructor overloading works in TypeScript, why multiple constructor bodies are not allowed, and how to define overload signatures with a single implementation. You will also see practical examples, common mistakes, and a small project to reinforce the pattern.
Concept
In TypeScript, constructors can be overloaded, but not in the same way as in some other languages such as Java or C#.
The key rule is:
- You may write multiple constructor signatures
- But you must provide exactly one constructor implementation
That means this is allowed in TypeScript:
class Example {
constructor();
constructor(name: string);
constructor(name?: string) {
// one real constructor body
}
}
But this is not allowed:
class Example {
constructor() {}
constructor(name: string) {}
}
TypeScript only lets a class have one actual runtime constructor because JavaScript itself only has one constructor function per class.
Why this matters
TypeScript adds type information on top of JavaScript. Overloads are mainly a type-checking feature. They help describe different valid ways to call the same constructor, while still compiling down to one JavaScript constructor.
This matters in real programming because classes often need flexible creation patterns, such as:
- creating an object with no arguments
- creating it from a configuration object
- creating it from separate values
Constructor overloads let you support these cases cleanly while keeping strong type safety.
Mental Model
Think of a TypeScript constructor like a single front door to a building.
You can put several signs outside the door saying:
- "You may enter with no arguments"
- "You may enter with a box object"
- "You may enter with x, y, width, and height"
Those signs are the overload signatures.
But inside the building, there is still only one actual doorway. That doorway is the single constructor implementation.
So overloads describe the allowed ways to arrive, but one constructor body handles all the cases.
Syntax and Examples
Basic syntax
In TypeScript, constructor overloading looks like this:
class Box {
x: number;
y: number;
width: number;
height: number;
constructor();
constructor(obj: IBox);
constructor(obj?: IBox) {
if (obj) {
this.x = obj.x;
this.y = obj.y;
this.width = obj.width;
this.height = obj.height;
} else {
this.x = 0;
this.y = 0;
this.width = 0;
this.height = 0;
}
}
}
Example using your IBox interface
interface IBox {
x: number;
y: number;
height: number;
width: number;
}
class Box {
public x: number;
public y: number;
public height: number;
public width: number;
constructor();
constructor(obj: IBox);
constructor(obj?: IBox) {
if (obj) {
this.x = obj.x;
this.y = obj.y;
this.height = obj.height;
this.width = obj.width;
} else {
this.x = 0;
this.y = 0;
this.width = 0;
this.height = 0;
}
}
}
const a = new Box();
const b = new Box({ x: 10, y: 20, width: 30, height: 40 });
What this does
constructor();declares thatnew Box()is validconstructor(obj: IBox);declares thatnew Box(someBox)is validconstructor(obj?: IBox)is the one implementation that handles both cases
Another example with multiple parameter shapes
class Point {
x: number;
y: number;
constructor();
constructor(x: number, y: number);
constructor(x?: number, y?: number) {
this.x = x ?? 0;
this.y = y ?? 0;
}
}
const p1 = new Point();
const p2 = new Point(5, 8);
The implementation uses optional parameters because it must be flexible enough to support all declared overloads.
Step by Step Execution
Consider this code:
interface IBox {
x: number;
y: number;
width: number;
height: number;
}
class Box {
x: number;
y: number;
width: number;
height: number;
constructor();
constructor(obj: IBox);
constructor(obj?: IBox) {
if (obj) {
this.x = obj.x;
this.y = obj.y;
this.width = obj.width;
this.height = obj.height;
} else {
this.x = 0;
this.y = 0;
this.width = 0;
this.height = 0;
}
}
}
const box1 = new Box();
const box2 = new Box({ x: 1, y: 2, width: 3, height: 4 });
What happens with new Box()
- TypeScript checks the overload signatures.
- It sees that
constructor();matches. - The actual implementation runs with
objasundefined. - The
if (obj)check is false. - Default values are assigned:
x = 0y = 0width = 0height = 0
What happens with new Box({ x: 1, y: 2, width: 3, height: 4 })
- TypeScript checks the overload signatures.
- It sees that
constructor(obj: IBox);matches. - The implementation runs with
objholding the provided object. - The
if (obj)check is true. - Values are copied from the object into the class properties.
Important detail
The overload signatures are only used by TypeScript during type checking. At runtime, JavaScript only sees one constructor function.
Real World Use Cases
Constructor overloads are useful when a class can be created in more than one valid way.
Common scenarios
Default or configured object creation
const defaultBox = new Box();
const configuredBox = new Box({ x: 5, y: 5, width: 100, height: 50 });
Useful for UI components, game objects, and drawing libraries.
Building domain models
A class may allow either:
- no arguments for safe defaults
- an object argument for existing data
This is common in API response models, settings objects, and DTO-style classes.
Wrapper classes
A class may support initialization from different input formats.
class User {
id: number;
name: string;
constructor();
constructor(data: { id: number; name: string });
constructor(data?: { id: number; name: string }) {
this.id = data?.id ?? 0;
this.name = data?.name ?? "";
}
}
Libraries and reusable components
Overloads help make APIs easier to use because callers can choose the form that fits their situation while still getting autocomplete and type checking.
Real Codebase Usage
In real projects, developers often use constructor overloads together with a few common patterns.
1. Default values
A constructor often accepts optional input and fills in missing values.
class Config {
host: string;
port: number;
constructor();
constructor(data: Partial<Config>);
constructor(data?: Partial<Config>) {
this.host = data?.host ?? "localhost";
this.port = data?.port ?? 3000;
}
}
2. Validation inside the constructor
class User {
name: string;
constructor(name: string);
constructor(data: { name: string });
constructor(value: string | { name: string }) {
const name = typeof value === "string" ? value : value.name;
if (!name) {
throw new Error("Name is required");
}
this.name = name;
}
}
3. Guard clauses
Developers often branch early to keep constructor logic simple.
class Box {
x: number;
y: number;
constructor();
constructor(data: { x: number; y: number });
constructor(data?: { x: number; y: number }) {
if (!data) {
this.x = 0;
this.y = 0;
return;
}
this.x = data.x;
this.y = data.y;
}
}
4. Prefer object parameters when many values are needed
When constructors take many arguments, teams often switch to one object parameter because it is easier to read and maintain.
new Box({ x: 10, y: 20, width: 30, height: 40 });
This avoids mistakes caused by argument order.
Common Mistakes
1. Writing multiple constructor bodies
This is the original problem.
Incorrect
class Box {
constructor() {}
constructor(obj: IBox) {}
}
Why it fails
TypeScript allows multiple signatures, but only one implementation.
Correct
class Box {
constructor();
constructor(obj: IBox);
constructor(obj?: IBox) {}
}
2. Making the implementation narrower than the overloads
The implementation must be able to handle every overload.
Incorrect
class Box {
constructor();
constructor(obj: IBox);
constructor(obj: IBox) {
// fails because new Box() would not fit this implementation
}
}
Correct
class Box {
constructor();
constructor(obj: IBox);
constructor(obj?: IBox) {
// handles both cases
}
}
3. Forgetting runtime checks
Overload signatures do not automatically separate behavior at runtime.
Incorrect
class Box {
constructor();
constructor(obj: IBox);
constructor(obj?: IBox) {
this.x = obj.x; // error: obj may be undefined
}
}
Correct
class Box {
x: number = 0;
constructor();
constructor(obj: IBox);
constructor(obj?: IBox) {
if (obj) {
this.x = obj.x;
}
}
}
4. Exposing overloads that the implementation does not really support
If you declare an overload, your constructor logic should actually handle it correctly.
5. Using too many positional parameters
This is valid, but harder to read:
new Box(10, 20, 30, 40);
For many values, an object is usually clearer:
new Box({ x: 10, y: 20, width: 30, height: 40 });
Comparisons
| Concept | What it means | Good for | Limitation |
|---|---|---|---|
| Constructor overloads | Multiple allowed call signatures, one implementation | Flexible class creation | Needs runtime checks inside one constructor |
| Optional parameters | A parameter may be omitted | Simple variations | Less explicit when argument shapes differ a lot |
| Union types | A parameter may be one of several types | Supporting different input forms | Requires type narrowing |
| Static factory methods | Named creation methods like Box.fromObject() | Clear intent and readable APIs | More methods to maintain |
Constructor overloads vs optional parameters
class A {
constructor(x?: number) {}
}
This is enough when the only variation is whether one value is present.
Constructor overloads vs union types
class B {
constructor(value: string | number) {}
}
A union type works well when there is one parameter with multiple possible types.
Constructor overloads vs static factory methods
class Box {
constructor(public x: number, public y: number) {}
static fromObject(obj: { x: number; y: number }) {
return new Box(obj.x, obj.y);
}
}
Factory methods are often clearer when creation logic is more descriptive or more complex.
Use overloads when you want one class constructor with a few well-defined call patterns. Use factory methods when the different creation paths need meaningful names.
Cheat Sheet
class MyClass {
constructor();
constructor(value: string);
constructor(value?: string) {
// one implementation only
}
}
Rules
- You can declare multiple constructor signatures.
- You can only write one constructor body.
- The implementation must handle all overload cases.
- Overloads are checked by TypeScript at compile time.
- JavaScript runtime only gets one constructor.
Common pattern
constructor();
constructor(obj: SomeType);
constructor(obj?: SomeType) {
if (obj) {
// use obj
} else {
// defaults
}
}
Useful tools
- Optional parameters:
arg?: Type - Union types:
arg: A | B - Nullish coalescing:
value ?? defaultValue - Type guards:
typeof,in, custom checks
Good practice
- Prefer object parameters when there are many values.
- Keep overloads simple and predictable.
- Validate input if bad values would break the object.
Edge case
This is invalid:
constructor() {}
constructor(x: number) {}
This is valid:
constructor();
constructor(x: number);
constructor(x?: number) {}
FAQ
Can TypeScript have multiple constructors like Java or C#?
Not with multiple bodies. TypeScript supports multiple constructor signatures, but only one implementation.
Why does TypeScript report a duplicate constructor definition?
Because your class contains more than one actual constructor body. JavaScript only supports one constructor implementation.
Do constructor overloads exist at runtime?
No. They are a compile-time TypeScript feature. At runtime, only one JavaScript constructor exists.
When should I use overloads instead of optional parameters?
Use overloads when you want to describe distinct valid call patterns. Use optional parameters when the variation is simple.
Can I overload a constructor with different parameter types?
Yes, as long as the single implementation can handle all cases.
constructor(value: string);
constructor(value: number);
constructor(value: string | number) {}
Is using an object parameter better than many arguments?
Often yes, especially when there are three or more related values. It improves readability and reduces argument-order mistakes.
Should I use a constructor overload or a static factory method?
Use overloads for a small number of simple creation patterns. Use factory methods when the creation paths need clearer names or more complex logic.
Mini Project
Description
Build a small Rectangle class that can be created in two ways: with no arguments for a default rectangle, or with a configuration object containing position and size. This demonstrates how constructor overloads make class creation flexible while still using one implementation.
Goal
Create a Rectangle class that supports both default construction and object-based construction, and includes an area() method.
Requirements
- Create an interface for rectangle data with
x,y,width, andheight - Add constructor overload signatures for no arguments and for one object argument
- Implement one constructor that handles both cases safely
- Add an
area()method that returns width × height - Create and test at least two rectangle instances
Keep learning
Related questions
@Directive vs @Component in Angular: Differences, Use Cases, and When to Use Each
Learn the difference between @Directive and @Component in Angular, including use cases, examples, and when to choose each.
Accessing Input Value from EventTarget in TypeScript
Learn why EventTarget has no value property in TypeScript and safely read values from HTML input events in Angular applications.
Angular (change) vs (ngModelChange): What’s the Difference?
Learn the difference between Angular (change) and (ngModelChange), when each fires, and which one to use in forms and inputs.