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, "charlie@example.com");
// 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: 'john@example.com' });
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.

Bijay Kumar is an experienced Python and AI professional who enjoys helping developers learn modern technologies through practical tutorials and examples. His expertise includes Python development, Machine Learning, Artificial Intelligence, automation, and data analysis using libraries like Pandas, NumPy, TensorFlow, Matplotlib, SciPy, and Scikit-Learn. At PythonGuides.com, he shares in-depth guides designed for both beginners and experienced developers. More about us.