How to fix Duplicate constructor definition in TypeScript

TypeScriptINTERMEDIATEMEDIUMUpdated Dec 8, 2025· 127 views

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
  1. Identify and remove duplicate constructor implementations
  2. Implement constructor overloading with signatures
  3. Handle different parameter types in the implementation
  4. Use static factory methods as an alternative
typescript — error
// 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
Duplicate 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

  1. Defining multiple constructors with implementation bodies in the same class
  2. Copy-pasting constructor code during refactoring without removing the original
  3. Attempting to use Java/C#-style constructor overloading patterns in TypeScript
  4. Adding a second constructor signature with a body instead of just the signature
  5. Merging code from multiple sources that each define a constructor
  6. Misunderstanding the difference between constructor overload signatures and implementation

How to fix "Duplicate constructor definition"

1Identify and remove duplicate constructor implementations

First, locate all constructor definitions in your class. Look for multiple blocks with implementation bodies:

typescript
// 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

Remove all but one constructor, keeping the one with the most complete logic, or the one you plan to expand into a single unified implementation.

2Implement constructor overloading with signatures

Use TypeScript's constructor overload pattern: declare multiple signatures (without bodies) followed by a single implementation:

typescript
// 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 age

The implementation signature must be compatible with all overload signatures. Use optional parameters (?) or union types (string | number) to accept all variations.

3Handle different parameter types in the implementation

When overloads accept different types, use type guards to determine which overload was called:

typescript
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 string

The implementation uses typeof checks or other type guards to branch based on which overload signature was matched.

4Use static factory methods as an alternative

For complex initialization logic, consider static factory methods instead of constructor overloading:

typescript
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.

5Merge constructor logic into a single implementation

If you have multiple constructors with different logic, combine them into one implementation:

typescript
// 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 99

Use 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
// 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:

typescript
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:

typescript
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:

typescript
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:

typescript
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:

typescript
// 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