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.
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 = ;
. = ;
}
}
}
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 {
. = ;
. = ;
. = ;
. = ;
}
}
}
box1 = ();
box2 = ({ : , : , : , : });
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: ; name: });
() {
. = data?. ?? ;
. = data?. ?? ;
}
}
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();
() {
name = value === ? value : value.;
(!name) {
();
}
. = name;
}
}
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: );
() {
}
}
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() {}
}
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
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.
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.
Angular Dependency Injection: Fix "Can't Resolve All Parameters for Component" Errors
Learn why Angular shows "Can't resolve all parameters for component" and how to fix service injection issues in components.