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.
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:
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.
10 20
0 0When 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.
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:
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:
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:
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.
10 20
30 40The 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:
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:
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.
Rex German ShepherdIf 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:
'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:
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:
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.
SafeGreeterThe 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:
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:
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:
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.
10 20This 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
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.
Frequently Asked Questions
Can a TypeScript constructor have a return type annotation?
Can I use type parameters on a constructor?
Do I need to call super() in a derived class constructor?
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.
More in this topic
TypeScript Compiler Explained
The TypeScript compiler (tsc) turns .ts files into .js files while checking types. Learn what it does, how to use it, and how it fits your workflow.
Set Up ESLint for TypeScript
Install and configure ESLint with typescript-eslint to catch bugs and enforce consistent code style in your TypeScript project.
Pick Utility Type in TypeScript
Pick creates a new type by selecting only the properties you name from an existing type. Use it to build focused types for specific views, API responses, or function parameters.