How to fix Duplicate constructor definition in TypeScript
This TypeScript error occurs when you define multiple constructor implementations in a single class. Unlike some languages, TypeScript allows multiple constructor signatures for overloading but requires only one implementation that handles all cases.
Quick fix
Fixes TS2392
// WRONG - Multiple constructor implementations
class Person {
name: string;
age: number;
constructor() {
this.name = "Unknown";
this.age = 0;
}
constructor(name: string) {
this.name = name;
this.age = 0;
}
}
// Error: Duplicate constructor definition- Identify and remove duplicate constructor implementations
- Implement constructor overloading with signatures
- Handle different parameter types in the implementation
- Use static factory methods as an alternative
// WRONG - Multiple constructor implementationsclass Person {name: string;age: number;constructor() {this.name = "Unknown";this.age = 0;}constructor(name: string) {this.name = name;this.age = 0;}}// Error: Duplicate constructor definitionDuplicate constructor definition
What this error means
The "Duplicate constructor definition" error (TS2392) appears when you attempt to define multiple constructor implementations in a TypeScript class. This is a fundamental difference from languages like Java or C# that support traditional constructor overloading with multiple separate implementations. TypeScript does support constructor overloading, but with a specific pattern: you can declare multiple constructor signatures (type declarations without bodies), but you must provide exactly one constructor implementation that handles all the overload cases. This design aligns with JavaScript's runtime behavior, where classes can only have one actual constructor function. TypeScript's type system adds the ability to describe different ways to call that constructor, but the underlying JavaScript still needs a single implementation. The error typically occurs when developers familiar with other languages try to write multiple constructor implementations, or when accidentally duplicating a constructor during refactoring.
Typical symptoms
- Compilation fails with 'Duplicate constructor definition' error
- Error code TS2392 appears in TypeScript compiler output
- IDE shows red underlines on the second constructor declaration
- Build fails even though the class logic seems correct
- Error message: 'Multiple constructor implementations are not allowed'
- Class has two or more constructor blocks with implementation bodies
Common causes
- Defining multiple constructors with implementation bodies in the same class
- Copy-pasting constructor code during refactoring without removing the original
- Attempting to use Java/C#-style constructor overloading patterns in TypeScript
- Adding a second constructor signature with a body instead of just the signature
- Merging code from multiple sources that each define a constructor
- Misunderstanding the difference between constructor overload signatures and implementation
How to fix "Duplicate constructor definition"
First, locate all constructor definitions in your class. Look for multiple blocks with implementation bodies:
// WRONG - Multiple constructor implementations
class Person {
name: string;
age: number;
constructor() {
this.name = "Unknown";
this.age = 0;
}
constructor(name: string) {
this.name = name;
this.age = 0;
}
}
// Error: Duplicate constructor definitionRemove all but one constructor, keeping the one with the most complete logic, or the one you plan to expand into a single unified implementation.
Use TypeScript's constructor overload pattern: declare multiple signatures (without bodies) followed by a single implementation:
// CORRECT - Constructor overloads with single implementation
class Person {
name: string;
age: number;
// Overload signatures (no implementation bodies)
constructor();
constructor(name: string);
constructor(name: string, age: number);
// Single implementation that handles all overloads
constructor(name?: string, age?: number) {
this.name = name ?? "Unknown";
this.age = age ?? 0;
}
}
// All of these work:
const p1 = new Person(); // Uses default values
const p2 = new Person("Alice"); // name only
const p3 = new Person("Bob", 30); // name and ageThe implementation signature must be compatible with all overload signatures. Use optional parameters (?) or union types (string | number) to accept all variations.
When overloads accept different types, use type guards to determine which overload was called:
class Point {
x: number;
y: number;
// Overload: accept two numbers
constructor(x: number, y: number);
// Overload: accept a string like "10,20"
constructor(coords: string);
// Implementation handles both
constructor(xOrCoords: number | string, y?: number) {
if (typeof xOrCoords === "string") {
// Parse string format
const [x, y] = xOrCoords.split(",").map(Number);
this.x = x;
this.y = y;
} else {
// Use number parameters
this.x = xOrCoords;
this.y = y!;
}
}
}
const p1 = new Point(10, 20); // Using numbers
const p2 = new Point("10,20"); // Using stringThe implementation uses typeof checks or other type guards to branch based on which overload signature was matched.
For complex initialization logic, consider static factory methods instead of constructor overloading:
class User {
private name: string;
private email: string;
// Single private constructor
private constructor(name: string, email: string) {
this.name = name;
this.email = email;
}
// Static factory methods for different creation patterns
static fromCredentials(username: string, password: string): User {
// Authenticate and create user
const email = `${username}@example.com`;
return new User(username, email);
}
static fromToken(token: string): User {
// Parse JWT or session token
const { name, email } = parseToken(token);
return new User(name, email);
}
static guest(): User {
return new User("Guest", "guest@example.com");
}
}
// Clear, explicit creation methods
const user1 = User.fromCredentials("alice", "pass123");
const user2 = User.fromToken("eyJhbGc...");
const user3 = User.guest();This pattern provides clearer intent and allows different initialization logic without complex overload signatures.
If you have multiple constructors with different logic, combine them into one implementation:
// Before (WRONG):
class Product {
id: string;
price: number;
constructor() {
this.id = generateId();
this.price = 0;
}
constructor(price: number) {
this.id = generateId();
this.price = price;
}
}
// After (CORRECT):
class Product {
id: string;
price: number;
constructor(price: number = 0) {
this.id = generateId();
this.price = price;
}
}
const p1 = new Product(); // Uses default price 0
const p2 = new Product(99); // Sets price to 99Use default parameter values, optional parameters, or conditional logic to handle different initialization scenarios.
Advanced notes
### Why TypeScript Differs from Java/C#
In Java or C#, you can write multiple constructors with different bodies:
// Java - this is valid
class Person {
String name;
int age;
Person() {
this.name = "Unknown";
this.age = 0;
}
Person(String name) {
this.name = name;
this.age = 0;
}
Person(String name, int age) {
this.name = name;
this.age = age;
}
}TypeScript can't do this because it compiles to JavaScript, which only supports a single constructor per class. TypeScript's overload signatures are purely compile-time type checks—they don't generate multiple runtime constructors.
### Constructor Overload Signature Compatibility
The implementation signature must be compatible with all overload signatures. This means:
class Example {
// Overload signatures
constructor(x: string);
constructor(x: number);
// Implementation must accept both string and number
constructor(x: string | number) {
// Implementation
}
}If your implementation signature is too narrow, you'll get an error:
class Example {
constructor(x: string);
constructor(x: number);
constructor(x: string) {
// Error: This overload signature is not compatible with its implementation signature
}
}The implementation signature doesn't need to be an overload itself—it's automatically considered the implementation if it has a body.
### Using Options Objects Instead
For classes with many optional parameters, consider using an options object instead of multiple overloads:
interface UserOptions {
name?: string;
email?: string;
age?: number;
role?: "admin" | "user";
}
class User {
name: string;
email: string;
age: number;
role: "admin" | "user";
constructor(options: UserOptions = {}) {
this.name = options.name ?? "Unknown";
this.email = options.email ?? "";
this.age = options.age ?? 0;
this.role = options.role ?? "user";
}
}
// Clean, self-documenting usage
const user = new User({
name: "Alice",
role: "admin",
age: 30,
});This approach scales better than overloads when you have many optional configurations.
### Abstract Classes and Constructors
Abstract classes can have constructor overloads, but the same rules apply:
abstract class Animal {
name: string;
constructor();
constructor(name: string);
constructor(name?: string) {
this.name = name ?? "Unknown";
}
abstract makeSound(): void;
}
class Dog extends Animal {
constructor(name: string) {
super(name); // Calls parent constructor
}
makeSound() {
console.log("Woof!");
}
}Derived classes don't inherit constructor overloads—they must call super() with arguments compatible with the parent's implementation signature.
### Generic Class Constructors
Generic classes can use overloads, but you can't overload based on type parameters:
// WRONG - Can't overload on generic type
class Box<T> {
constructor(value: T); // when T is string
constructor(value: T); // when T is number - This is a duplicate!
}
// CORRECT - Single constructor with generic type
class Box<T> {
value: T;
constructor(value: T) {
this.value = value;
}
}
const stringBox = new Box<string>("hello");
const numberBox = new Box<number>(42);Type parameters are erased at runtime, so overloading on them is impossible.
Frequently asked questions
- What does "TS2392" mean?
- The "Duplicate constructor definition" error (TS2392) appears when you attempt to define multiple constructor implementations in a TypeScript class. This is a fundamental difference from languages like Java or C# that support traditional constructor overloading with multiple separate implementations. TypeScript does support constructor overloading, but with a specific pattern: you can declare multiple constructor signatures (type declarations without bodies), but you must provide exactly one constructor implementation that handles all the overload cases.
- How do I fix "TS2392"?
- This TypeScript error occurs when you define multiple constructor implementations in a single class. Unlike some languages, TypeScript allows multiple constructor signatures for overloading but requires only one implementation that handles all cases.
- What causes "TS2392"?
- Defining multiple constructors with implementation bodies in the same class Copy-pasting constructor code during refactoring without removing the original Attempting to use Java/C#-style constructor overloading patterns in TypeScript
- Is the "TS2392" error serious?
- It is a medium-severity error — more of a blocker than a risk. The steps above resolve it.
Official resources & further reading
- 📚TypeScript Official Documentation
- 📖TypeScript Handbook - Classes(Official Docs)
- 🐙
- 🐙