Constructors in TypeScript

TypeScript constructors initialize new class instances. Learn how to type constructor parameters, use default values, write overloaded constructors, and handle the this context safely.

8 min read

A TypeScript constructor is a special method that runs when you call new on a class. It receives arguments, sets up the instance, and returns the new object. You type constructor parameters the same way you type function parameters.

Here is a constructor with typed parameters and default values:

typescripttypescript
class Point {
  x: number;
  y: number;
 
  constructor(x: number = 0, y: number = 0) {
    this.x = x;
    this.y = y;
  }
}
 
const p1 = new Point(10, 20);
const p2 = new Point();
console.log(p1.x, p1.y);
console.log(p2.x, p2.y);

The first line prints the explicit arguments. The second prints the defaults, since p2 was created without passing any arguments to the constructor at all.

texttext
10 20
0 0

When you call new Point() with no arguments, the defaults kick in and the point starts at (0, 0). The constructor is the bridge between raw arguments and a fully initialized instance. Every field must be either initialized at declaration or assigned in the constructor body.

typescripttypescript
class User {
  name: string;
  email: string;
  createdAt: number = Date.now();
 
  constructor(name: string, email: string) {
    this.name = name;
    this.email = email;
  }
}
 
const user = new User("Ada", "ada@example.com");
console.log(user.name);

The name field comes from the constructor argument. The createdAt field gets its value automatically from the initializer, without the caller ever passing a timestamp:

typescripttypescript
console.log(user.createdAt);

The name and email fields are set from constructor arguments. The createdAt field has a default initializer, so it does not need a constructor assignment.

Unlike name and email, createdAt gets its value automatically when the instance is created. You do not pass it to the constructor because the timestamp should be set at creation time, not provided by the caller.

Constructor Overloads

A class can define multiple constructor signatures. This lets one constructor accept different argument shapes, similar to function overloads, so callers can pass either two numbers or a single formatted string:

typescripttypescript
class Point {
  x: number = 0; y: number = 0;
  constructor(x: number, y: number);
  constructor(xy: string);
  constructor(x: string | number, y: number = 0) {
    if (typeof x === "string") {
      const [nx, ny] = x.split(",").map(Number);
      this.x = nx; this.y = ny;
    } else {
      this.x = x; this.y = y;
    }
  }
}

The first two signatures are overloads that define valid call patterns. The third signature is the implementation. Let us create instances using both overloaded forms:

typescripttypescript
const p1 = new Point(10, 20);
const p2 = new Point("30,40");
console.log(p1.x, p1.y);
console.log(p2.x, p2.y);

Both calls type-check because they match one of the two overload signatures. Internally, the implementation parses whichever shape it receives.

texttext
10 20
30 40

The first two lines inside the class are overload signatures. They define the valid ways to call the constructor.

The third line is the implementation signature, and it is the only one that actually runs. Only the implementation signature counts for field initialization checks.

Two Constructor Restrictions

Constructors differ from regular functions in two important ways.

No type parameters. If your class needs to be generic, put the type parameter on the class, not the constructor. For example, a generic Box class uses the class-level type parameter:

typescripttypescript
class Box<Item> {
  contents: Item;
 
  constructor(value: Item) {
    this.contents = value;
  }
}
 
const stringBox = new Box("hello");

Writing the type parameter on the constructor itself is a syntax error. The type parameter belongs to the class so that every instance shares the same type relationship.

No return type annotation. The constructor always returns the class instance. Adding a return type annotation produces a compiler error, because TypeScript already knows the return type is the class itself.

Super Calls in Derived Classes

When a class extends another, the derived constructor must call super() before it can use this:

typescripttypescript
class Animal {
  name: string;
  constructor(name: string) { this.name = name; }
}
class Dog extends Animal {
  breed: string;
  constructor(name: string, breed: string) {
    super(name);
    this.breed = breed;
  }
}
 
const dog = new Dog("Rex", "German Shepherd");
console.log(dog.name, dog.breed);

This prints Rex German Shepherd, since dog.name comes from the base class constructor called through super, and dog.breed comes from the Dog constructor itself.

texttext
Rex German Shepherd

If you try to access this before calling super(), TypeScript immediately reports a compile error. The derived class constructor must call the parent constructor first, otherwise the instance is not properly initialized:

texttext
'super' must be called before accessing 'this' in the constructor of a derived class.

JavaScript itself throws a runtime error for this in ES6 classes, so TypeScript catches it at compile time instead of letting it crash in production.

The this Context Problem

JavaScript's this depends on how a function is called, not where it is defined. A method extracted from a class can lose its context:

typescripttypescript
class Greeter {
  name = "Greeter";
  greet() {
    return this.name;
  }
}
 
const g = new Greeter();
const extracted = g.greet;
console.log(extracted());

This code compiles but produces undefined at runtime because this becomes the global object (or undefined in strict mode) when greet is called standalone.

Arrow function property: binds this permanently:

typescripttypescript
class SafeGreeter {
  name = "SafeGreeter";
  greet = () => {
    return this.name;
  };
}
 
const g = new SafeGreeter();
const extracted = g.greet;
console.log(extracted());

This prints SafeGreeter even after extraction, because the arrow function captured this from the surrounding instance when the class was constructed.

texttext
SafeGreeter

The trade-off: each instance gets its own copy of the arrow function, which uses more memory than a shared prototype method.

This parameter: makes incorrect usage a compile error without the memory cost:

typescripttypescript
class StrictGreeter {
  name = "StrictGreeter";
  greet(this: StrictGreeter) {
    return this.name;
  }
}
 
const g = new StrictGreeter();
g.greet();
const extracted = g.greet;
extracted();

TypeScript reports this error at compile time because the extracted method is called standalone, so its this context no longer matches the StrictGreeter type it expects:

texttext
The 'this' context of type 'void' is not assignable to method's 'this' of type 'StrictGreeter'.

The this parameter is erased during compilation. It exists only for type checking. Choose arrow properties when you need guaranteed correctness at runtime, and this parameters when you want to avoid the per-instance memory cost.

Parameter Properties: A Shortcut

TypeScript offers a shorthand for the common pattern of declaring a field and assigning it from a constructor parameter. Prefix a constructor argument with public, private, protected, or readonly:

typescripttypescript
class Point {
  constructor(
    public x: number,
    public y: number
  ) {}
}
 
const p = new Point(10, 20);
console.log(p.x, p.y);

This prints the same 10 20 as the earlier Point examples, but with far less code, since the public prefix declares and assigns each field in one step.

texttext
10 20

This replaces the three-step pattern: declare field, declare constructor parameter, and write this.x = x. For a deeper look, see parameter properties in TypeScript.

Common Mistakes

Accessing this before super(). Always call super(...) as the first statement in a derived class constructor before using this.

Forgetting to assign a field in the constructor. If a field has no initializer and is not assigned in the constructor, strictPropertyInitialization produces an error. Either add an initializer, assign it in the constructor, or use ! if external code handles it.

Extracting a method and losing this. Use an arrow function property or a this parameter to keep the context safe. For more on member visibility, see public private and protected in TypeScript.

Rune AI

Rune AI

Key Insights

  • A constructor initializes a class instance. Type parameters, default values, and type annotations work the same as in functions.
  • Constructors cannot have type parameters (those go on the class) and cannot have return type annotations.
  • Constructor overloads let one constructor accept different argument shapes. Only the implementation signature counts at runtime.
  • In a derived class, super() must be called before accessing this.
  • Use a this parameter to prevent a method from being called with the wrong context.
RunePowered by Rune AI

Frequently Asked Questions

Can a TypeScript constructor have a return type annotation?

No. Constructors cannot have return type annotations. The return type is always the class instance type. TypeScript reports an error if you try to add one.

Can I use type parameters on a constructor?

No. Type parameters belong on the class declaration, not on the constructor. If you need a generic class, put the type parameter on the class, not the constructor.

Do I need to call super() in a derived class constructor?

Yes, if the class extends a base class. You must call super() before accessing this in the derived constructor body. TypeScript enforces this with a compiler error if you forget.

Conclusion

A TypeScript constructor turns raw arguments into a ready-to-use class instance. You type each parameter, add default values when sensible, and use overloads when one constructor must accept different argument shapes. The constructor is also where you call super() in derived classes and where strictPropertyInitialization expects every uninitialized field to be assigned.