VOOZH about

URL: https://blog.logrocket.com/writing-constructor-typescript/

⇱ Writing a constructor in TypeScript - LogRocket Blog


2022-01-10
1279
#typescript
Kealan Parr
85447
👁 Image

See how LogRocket's Galileo AI surfaces the most severe issues for you

No signup required

Check it out

Any mature TypeScript codebase will typically make heavy use of interfaces.

👁 TypeScript Logo Over a Colorful Thrown-Paint Background

They are, after all, the building blocks of adding static compile-time checks on your code, and they ensure you are sensibly using the collective/custom types you define within your code.

Interface syntax is simple, and interfaces offer a host of advantages when used in your code, such as:

  • Produce simple, easily understood error messages
  • Sometimes compile faster than type definitions
  • Used heavily by the TypeScript community, so they are a common best practice, (the TypeScript documentation utilizes them heavily also)
  • The TypeScript team endorses interfaces, too. Daniel Rosenwasser, TypeScript’s program manager, has endorsed interfaces over type

Let’s learn more about constructors and how we’ll use constructors in interfaces:

🚀 Sign up for The Replay newsletter

The Replay is a weekly newsletter for dev and engineering leaders.

Delivered once a week, it's your curated guide to the most important conversations around frontend dev, emerging AI tools, and the state of modern software.

Constructors

Constructors are also a code feature heavily used in TypeScript codebases too.

The TypeScript docs have a great example of constructor usage:

class Greeter {
 greeting: string;

 constructor(message: string) {
 this.greeting = message;
 }

 greet() {
 return "Hello, " + this.greeting;
 }
}

let greeter = new Greeter("world");

Constructors essentially allow the creation of objects from classes.

Classes act like a blueprint for typing the objects you create with the necessary properties and methods.

Constructor dependency injection

Constructors often make use of a code technique called dependency injection — that is key to utilizing them to their fullest potential.

This is where the dependencies necessary for the object we’re going to create are passed into the constructor.

In the above example, we see we pass in the message argument into the constructor to allow unique customization of the object:

let computer = new Greeter("world"); // Hello world
let farewell = new Greeter("and goodbye!"); // Hello and goodbye!

The ability for the same class (the Greeter class) to provide different results from a method call is often called polymorphism.

Multiple constructors

A final important thing to remember when using constructors is that you cannot use multiple constructors implementations — like you can in other object-orientated languages.

An example of multiple constructors would be like so:

class Animal {

 constructor() {
 }

 constructor() {
 }
}

The above code won’t compile and logs the error Multiple constructor implementations are not allowed.

If you need to use multiple constructors to provide different functionality from a base class, there are ways of doing this, but you can only use one implementation.

If you need different constructors — there are ways to work around this though, you can type multiple constructors — you just can’t implement them.


Over 200k developers use LogRocket to create better digital experiences

👁 Image
Learn more →

A real-world example would look like:

class DemoClassTest {
 constructor(x : string, y:string);
 constructor(x : number);
 constructor(x : number, y:string, z:string);
 constructor(...myarray: any[]) {

 if (myarray.length === 2) {
 console.log('two argument constructor called here !!');
 return;
 }
 if (myarray.length === 3) {
 console.log('three argument constructor called here !!');
 return;
 }
 if (myarray.length === 1) {
 console.log('one argument constructor called here !!');
 return;
 }
 }
}
let a = new DemoClassTest('hello', 'bye');
let b = new DemoClassTest(1);
let c = new DemoClassTest(100, 'str1', 'str2');

Using a constructor on an interface

We’ve discussed the more common use cases for utilizing constructors, but their functionality doesn’t end there.

Sometimes, as part of a design pattern or for certain use cases, developers may want to specifically create an instance variable from an interface.

A simple example of an interface we might want to construct could be:

interface Animal {
 numLegs: number,
 wings: boolean
}

But how we add a constructor to this type is not clear.

Even more confusingly, in the compiled JavaScript, the interface won’t even exist. It only exists to check our types and then will be totally removed, thanks to a process called type erasure.

So, let’s start with a failing example and solve it iteratively:

interface InterfaceWithConsturctor {
 config: string;
 constructor(config: string): { config: string };
}

class ConfigClass implements InterfaceWithConsturctor {

 public config = '';

 constructor(config: string) {
 this.config = config;
 }
}

The error we are currently facing is:

- Class 'ConfigClass' incorrectly implements interface 'InterfaceWithConsturctor'.
- Types of property 'constructor' are incompatible.
- Type 'Function' is not assignable to type '(config: string) => { config: string; }'.
- Type 'Function' provides no match for the signature '(config: string): { config: string; }'.

Even though our two constructors match (in the interface versus in the class implementing the interface), it throws an error and won’t compile.

You can see in the two code examples that they are using the same type, and, by the looks of it, should compile just fine.

Adding a constructor to a TypeScript interface

The docs include an example covering this exact scenario.

Our earlier examples are failing because, according to the docs, “when a class implements an interface, only the instance side of the class is checked. Because the constructor sits in the static side, it is not included in this check.”

This reads weirdly, but it essentially means that the constructor isn’t an instance type method.

By instance type method, we’re referring to a “normal” function that would be called with obj.funcCall() existing on the object instance, as a result of using the new keyword. The constructor actually belongs to the static type.


More great articles from LogRocket:


In this case, the static type means the type it belongs to, without instantiating it, e.g., InterfaceWithConsturctor.

To fix this, we need to create two interfaces: one for the static type methods/properties and one for the instance type methods.

Our new working example, inspired by the engineering lead of TypeScript, looks like this.

interface ConfigInterface {
 config: string;
}
interface InterfaceWithConsturctor {
 new(n: string): { config: string };
}
class Config implements ConfigInterface {
 public config: string;
 constructor (config: string) {
 this.config = config;
 }
}
function setTheState(n: InterfaceWithConsturctor) {
 return new n('{ state: { clicked: true, purchasedItems: true } }');
}
console.log(setTheState(Config).config);

This now logs as { state: { clicked: true, purchasedItems: true } }.

Benefits to using TypeScript interface constructors

By using this language feature, you can create more composable objects that don’t rely on inheritance to share code.

With a constructor on the interface, you can specify that all of your types must have certain methods/properties (normal interface compliance) but also control how the types get constructed by typing the interface like you would with any other method/property.

We are relying on abstractions rather than concretions. There’s an example from the old TypeScript docs to highlight this.

The old docs are still valid TypeScript, and they’re a really clear example of what we’re discussing – so I have kept the legacy URL for clarity.

//
// Instance and static interfaces
//
interface ClockConstructor {
 new (hour: number, minute: number): ClockInterface;
}
interface ClockInterface {
 tick(): void;
}

function createClock(
 ctor: ClockConstructor,
 hour: number,
 minute: number
): ClockInterface {
 return new ctor(hour, minute);
}

//
// Clock implementation classes
//
class DigitalClock implements ClockInterface {
 constructor(h: number, m: number) {}
 tick() {
 console.log("beep beep");
 }
}
class AnalogClock implements ClockInterface {
 constructor(h: number, m: number) {}
 tick() {
 console.log("tick tock");
 }
}

// Now we allow relatively generic (but typed!) creation of Clock classes
let digital = createClock(DigitalClock, 12, 17);
let analog = createClock(AnalogClock, 7, 32);

Here, we are creating a strictly typed constructor function with the arguments we need other classes to use, but at the same time, allowing it to be generic enough it fits multiple use-cases.

It also ensures we are keeping low coupling, high cohesion in our code.

Conclusion

I hope this has explained not only how to add a constructor onto an interface, but some of the common use cases for when and why it might be done, as well as the syntax of how you can achieve it.

It is a common enough occurrence that the docs even explain the basic approach, and it is useful to understand the two sides of static versus instance scope in the underlying JavaScript/TypeScript world.

LogRocket understands everything users do in your web and mobile apps.

👁 LogRocket Dashboard Free Trial Banner

LogRocket lets you replay user sessions, eliminating guesswork by showing exactly what users experienced. It captures console logs, errors, network requests, and pixel-perfect DOM recordings — compatible with all frameworks, and with plugins to log additional context from Redux, Vuex, and @ngrx/store.

With Galileo AI, you can instantly identify and explain user struggles with automated monitoring of your entire product experience.

Modernize how you understand your web and mobile apps — start monitoring for free.

👁 Image
👁 Image
👁 Image

Stop guessing about your digital experience with LogRocket

Get started for free

Recent posts:

How to add authentication to a React Native app with Better Auth

Learn how to build a full React Native auth system using Better Auth and Expo — with email/password login, Google OAuth, session persistence, and protected routes.

👁 Image
Chinwike Maduabuchi
Jun 9, 2026 ⋅ 13 min read

AI dev tool power rankings & comparison [June 2026]

Compare the top AI development tools and models of June 2026. View updated rankings, feature breakdowns, and find the best fit for you.

👁 Image
Chizaram Ken
Jun 8, 2026 ⋅ 11 min read

How to check username availability at scale with Bloom filters

Learn how Bloom filters reduce database lookups for username availability checks while preserving correctness at scale.

👁 Image
Rosario De Chiara
Jun 8, 2026 ⋅ 6 min read

An advanced guide to Nuxt testing and mocking

Learn how to test Nuxt apps with Vitest, @nuxt/test-utils, runtime mocks, server route mocks, and Playwright e2e tests.

👁 Image
Sebastian Weber
Jun 5, 2026 ⋅ 15 min read
View all posts

Would you be interested in joining LogRocket's developer community?

Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.

Sign up now