When I first started working with TypeScript classes, I was searching for solutions to multiple ways of constructing an object in TypeScript, similar to those in Java. After searching, I discovered that TypeScript doesn’t support traditional constructor overloading. However, with an alternative approach that utilizes multiple constructor signatures and a single implementation, we can achieve the same output.
In this TypeScript tutorial, I’ll explain how constructor overloading works in TypeScript, with the help of real-time examples.
What is Constructor Overloading in TypeScript?
Constructor overloading is a feature that allows a class to have multiple constructor declarations with different parameter types or counts. This gives you flexibility in how objects can be created.
Unlike languages like C# or Java, TypeScript implements constructor overloading through a combination of declaration signatures and a single implementation.
Here’s a simple example of what constructor overloading looks like:
class Username {
name: string;
age: number;
email?: string;
// Constructor overloads
constructor(name: string);
constructor(name: string, age: number);
constructor(name: string, age: number, email: string);
// Single constructor implementation
constructor(name: string, age?: number, email?: string) {
this.name = name;
this.age = age ?? 0;
this.email = email;
}Test the method:
// Method to return user details as a string
getInfo(): string {
return `Name: ${this.name}\nAge: ${this.age}\nEmail: ${this.email ?? 'N/A'}`;
}
// Method to return user details as an object
toObject(): { name: string; age: number; email?: string } {
return {
name: this.name,
age: this.age,
email: this.email
};
}
}
const user1 = new Username("Alice");
const user2 = new Username("Bob", 28);
const user3 = new Username("Charlie", 35, "[email protected]");
// Print string output
console.log(user1.getInfo());
console.log(user2.getInfo());
console.log(user3.getInfo());
console.log("-----");
// Print object output
console.log(user1.toObject());
console.log(user2.toObject());
console.log(user3.toObject());Output:

Check out: Iterate Over Objects in TypeScript
Method 1: Basic Constructor Overloading
The simplest way to implement constructor overloading is to define multiple constructor signatures, followed by a single implementation constructor that handles all cases.
Here’s how I usually implement basic constructor overloading:
class Product {
id: number;
name: string;
price: number;
category?: string;
// Overload signatures
constructor(id: number, name: string, price: number);
constructor(id: number, name: string, price: number, category: string);
// Implementation
constructor(id: number, name: string, price: number, category?: string) {
this.id = id;
this.name = name;
this.price = price;
this.category = category;
console.log("Product created:");
console.log(` ID: ${this.id}`);
console.log(` Name: ${this.name}`);
console.log(` Price: $${this.price}`);
console.log(` Category: ${this.category ?? "N/A"}`);
console.log("--------------------");
}
}
// Usage examples
const laptop = new Product(1, "MacBook Pro", 1999);
const phone = new Product(2, "iPhone 13", 999, "Electronics");Output:

This approach works well for simple cases where you have optional parameters at the end of your parameter list.
Method 2: Constructor Overloading with Different Parameter Types
Sometimes you need to accept completely different parameter types in different constructors. Here’s how I handle this in my projects:
class Payment {
amount: number;
currency: string;
date: Date;
// Overload signatures
constructor(amount: number);
constructor(amount: number, currency: string);
constructor(paymentData: { amount: number; currency: string; date: Date });
// Implementation
constructor(
amountOrData: number | { amount: number; currency: string; date: Date },
currency?: string
) {
if (typeof amountOrData === 'object') {
this.amount = amountOrData.amount;
this.currency = amountOrData.currency;
this.date = amountOrData.date;
console.log("Payment created using object input:");
} else {
this.amount = amountOrData;
this.currency = currency || 'USD';
this.date = new Date();
console.log("Payment created using individual values:");
}
console.log(` Amount: ${this.amount}`);
console.log(` Currency: ${this.currency}`);
console.log(` Date: ${this.date.toDateString()}`);
console.log("--------------------");
}
}
// Usage examples
const payment1 = new Payment(99.99);
const payment2 = new Payment(149.99, "EUR");
const payment3 = new Payment({
amount: 299.99,
currency: "GBP",
date: new Date(2025, 6, 15) // Note: July (0-based month index)
});Output:

This pattern is particularly useful for complex objects where you want to provide both individual parameters and a configuration object option.
Check out: TypeScript Generic Object Types
Method 3: Factory Methods as an Alternative
Sometimes, traditional constructor overloading can become complex and hard to maintain. In these cases, I often turn to static factory methods:
class ShoppingCart {
items: Array<{product: string, quantity: number, price: number}>;
private constructor(items: Array<{product: string, quantity: number, price: number}> = []) {
this.items = items;
}
// Factory methods instead of constructor overloading
static empty(): ShoppingCart {
return new ShoppingCart();
}
static withItem(product: string, price: number): ShoppingCart {
return new ShoppingCart([{product, quantity: 1, price}]);
}
static fromJSON(json: string): ShoppingCart {
const data = JSON.parse(json);
return new ShoppingCart(data.items);
}
// Add methods to the class
addItem(product: string, quantity: number, price: number): void {
this.items.push({product, quantity, price});
}
getTotal(): number {
return this.items.reduce((sum, item) => sum + (item.price * item.quantity), 0);
}
}
// Usage examples
const emptyCart = ShoppingCart.empty();
const cartWithItem = ShoppingCart.withItem("American Football", 49.99);
const cartFromJSON = ShoppingCart.fromJSON('{"items":[{"product":"Baseball Cap","quantity":2,"price":24.99}]}');Output:

I find this approach particularly useful for complex initialization logic or when constructor parameters could be ambiguous.
Check out: Use Try Catch in TypeScript
Advanced Constructor Overloading Patterns
After years of TypeScript development, I’ve developed some advanced patterns for constructor overloading that work well in larger applications:
Handling Multiple Constructor Types with Union Types
class Address {
street: string;
city: string;
state: string;
zipCode: string;
// Overload signatures
constructor(fullAddress: string);
constructor(street: string, city: string, state: string, zipCode: string);
// Implementation
constructor(streetOrFullAddress: string, city?: string, state?: string, zipCode?: string) {
if (!city && !state && !zipCode) {
console.log("Creating address from full string...");
// Parse full address format: "123 Main St, San Francisco, CA 94107"
const parts = streetOrFullAddress.split(',').map(part => part.trim());
this.street = parts[0] || '';
this.city = parts[1] || '';
const stateZip = (parts[2] || '').split(' ');
this.state = stateZip[0] || '';
this.zipCode = stateZip[1] || '';
console.log("Parsed full address:");
} else {
console.log("Creating address from individual parts...");
this.street = streetOrFullAddress;
this.city = city || '';
this.state = state || '';
this.zipCode = zipCode || '';
}
console.log(` Street: ${this.street}`);
console.log(` City: ${this.city}`);
console.log(` State: ${this.state}`);
console.log(` Zip Code: ${this.zipCode}`);
console.log("--------------------");
}
toString(): string {
return `${this.street}, ${this.city}, ${this.state} ${this.zipCode}`;
}
}
// === Usage Examples ===
const address1 = new Address("123 Main St", "San Francisco", "CA", "94107");
const address2 = new Address("123 Main St, San Francisco, CA 94107");
console.log("Address 1:", address1.toString());
console.log("Address 2:", address2.toString());Output:

Using Discriminated Unions for Complex Constructors
type UserProps =
| { type: 'basic'; name: string; email: string }
| { type: 'social'; name: string; socialId: string; provider: 'Google' | 'Facebook' }
| { type: 'guest'; guestId: string };
class User {
name: string;
id: string;
accountType: 'basic' | 'social' | 'guest';
constructor(props: UserProps) {
switch(props.type) {
case 'basic':
this.name = props.name;
this.id = `user_${Math.random().toString(36).substring(2, 9)}`;
this.accountType = 'basic';
break;
case 'social':
this.name = props.name;
this.id = `${props.provider.toLowerCase()}_${props.socialId}`;
this.accountType = 'social';
break;
case 'guest':
this.name = 'Guest User';
this.id = props.guestId;
this.accountType = 'guest';
break;
}
}
}
// Usage examples
const basicUser = new User({ type: 'basic', name: 'John Doe', email: '[email protected]' });
const socialUser = new User({ type: 'social', name: 'Jane Smith', socialId: '123456789', provider: 'Google' });
const guestUser = new User({ type: 'guest', guestId: 'guest_abc123' });This pattern works exceptionally well when you have fundamentally different ways to construct an object, each requiring its own set of parameters.
Check out: Set Default Values in TypeScript Interfaces
Best Practices for Constructor Overloading
Through my years of working with TypeScript, I’ve developed these best practices for constructor overloading:
- Keep it simple: Only use overloading when it genuinely improves API usability.
- Use JSDoc comments: Document each overload signature to help IDE users understand the different options.
- Consider factory methods: For complex initialization logic, static factory methods are often clearer than constructor overloads.
- Avoid type assertion: Try to structure your implementation to avoid using
astype assertions. - Test all overloads: Ensure all constructor variations work as expected with dedicated tests.
Constructor overloading is a powerful TypeScript feature that can make your APIs more flexible and intuitive. By following these patterns and best practices, you can create classes that are both type-safe and easy to use.
I hope that, by following the above examples and methods, you have understood the concept of Constructor overloading in TypeScript.

I am Bijay Kumar, a Microsoft MVP in SharePoint. Apart from SharePoint, I started working on Python, Machine learning, and artificial intelligence for the last 5 years. During this time I got expertise in various Python libraries also like Tkinter, Pandas, NumPy, Turtle, Django, Matplotlib, Tensorflow, Scipy, Scikit-Learn, etc… for various clients in the United States, Canada, the United Kingdom, Australia, New Zealand, etc. Check out my profile.