Skip to content
TypeScript ts classes 4 min read

Constructors

A constructor is the special method that runs when you create an instance with new. In TypeScript it works like the JavaScript constructor but with typed parameters and strict field-initialization checks. Every field a class declares must be assigned a value either at its declaration site or inside the constructor, otherwise the compiler complains under strictPropertyInitialization. This page covers declaring constructors, initializing fields, handling optional and default parameters, and the rarely needed constructor overload syntax.

Declaring a constructor

A constructor uses the reserved name constructor, takes typed parameters, and assigns the class’s fields. You declare the fields with their types and then set them inside the constructor body.

class Point {
  x: number;
  y: number;

  constructor(x: number, y: number) {
    this.x = x;
    this.y = y;
  }
}

const origin = new Point(0, 0); // Point { x: 0, y: 0 }

The constructor never has an explicit return type — it always produces an instance of the class. Calling new Point(0, 0) allocates the object, runs the body, and returns the typed instance.

Strict property initialization

Under strict mode (specifically strictPropertyInitialization), every non-optional field must be definitely assigned. If a field is neither given a default nor set in the constructor, TypeScript reports an error.

class User {
  name: string; // ❌ Error: Property 'name' has no initializer
  age = 0;      // ✅ initialized at declaration

  constructor(name: string) {
    // forgot to assign this.name
  }
}

You can satisfy the checker by assigning in the constructor, providing a default, marking the field optional with ?, or using the definite assignment assertion ! when you know a value arrives elsewhere.

class Config {
  endpoint!: string; // definite assignment assertion: "trust me, it's set"

  constructor() {
    this.load();
  }
  private load() {
    this.endpoint = "https://api.example.com";
  }
}

Use the ! definite assignment assertion sparingly. It silences the compiler, so if the field is never actually set you get an undefined at runtime with no warning.

Optional and default parameters

Constructor parameters follow normal function rules: mark them optional with ? or give them defaults. A default value makes the parameter optional at the call site while keeping the field non-undefined.

class Rectangle {
  width: number;
  height: number;

  constructor(width: number, height = width) {
    this.width = width;
    this.height = height; // square when height omitted
  }
}

new Rectangle(10);    // 10 x 10
new Rectangle(10, 5); // 10 x 5

Here height defaults to width, producing a square. Because the default fills in a value, this.height is always a number, never undefined.

Constructor overloads

TypeScript supports constructor overloads: several typed signatures followed by one implementation. This lets a class accept different argument shapes while keeping each call site precisely typed.

class DateRange {
  start: Date;
  end: Date;

  constructor(range: { start: Date; end: Date });
  constructor(start: Date, end: Date);
  constructor(a: Date | { start: Date; end: Date }, b?: Date) {
    if (a instanceof Date) {
      this.start = a;
      this.end = b!;
    } else {
      this.start = a.start;
      this.end = a.end;
    }
  }
}

new DateRange(new Date(), new Date());
new DateRange({ start: new Date(), end: new Date() });

Only the overload signatures are visible to callers; the implementation signature must be broad enough to cover them all. Prefer a single flexible signature when possible — overloads add complexity.

When to skip the constructor

If a constructor only copies parameters onto fields, use parameter properties to remove the boilerplate. Adding an access modifier or readonly to a parameter declares and assigns the field automatically.

class Vector {
  constructor(
    public readonly x: number,
    public readonly y: number,
  ) {}
}

const v = new Vector(3, 4);
console.log(v.x, v.y); // 3 4

This is equivalent to declaring x and y plus assigning them, but far terser.

ApproachWhen to use
Explicit fields + assignmentLogic beyond simple copying; conditional init
Default/optional parametersSensible fallbacks; partially-specified construction
Constructor overloadsGenuinely distinct argument shapes
Parameter propertiesPure “store the argument” constructors

Best Practices

  • Keep constructors lightweight: assign fields and do minimal setup, not heavy I/O.
  • Enable strict so strictPropertyInitialization catches uninitialized fields.
  • Prefer defaults and parameter properties over manual this.x = x boilerplate.
  • Avoid throwing from constructors when an exception leaves a half-built object; use a factory method instead.
  • Reach for the ! definite assignment assertion only when initialization genuinely happens elsewhere.
  • Use overloads only for truly distinct shapes; otherwise a union parameter is simpler.
  • Make a constructor private to force creation through a static factory (e.g. singletons).
Last updated June 29, 2026
Was this helpful?