return inside a constructor
return in a constructor exists only to replace the instance being created with a different object. Without it the constructor implicitly returns this, and a returned primitive is simply ignored by the engine.
Theory
TL;DR
returnin a constructor is optional:thisis returned by default.- If you return an object, it replaces
thisand becomes the result ofnew. - If you return a primitive, it is ignored and the result is still
this. - In a derived class
super()must be called before anyreturn, otherwise you get aReferenceError. - The Singleton, Factory and Proxy patterns all build on this behaviour.
Quick example
class User {
constructor(name) {
this.name = name;
}
}
const u = new User("Tim");
console.log(u); // User { name: "Tim" }The normal behaviour of a constructor
When you write new User("Tim"), the engine does roughly this:
- Creates a new empty object
{}. - Sets the prototype of that object to
User.prototype. - Calls the
constructorwiththisreferring to that new object. - Returns
thisfrom the constructor by default.
In other words, if you return nothing, return this happens implicitly.
Returning an object replaces this
If return hands back an object, it replaces the created instance:
class User {
constructor(name) {
this.name = name;
return { custom: "another object" };
}
}
const u = new User("Tim");
console.log(u); // { custom: "another object" }Note that this.name = name still ran, that object just went nowhere. The returned object has neither User.prototype nor the class methods, so u instanceof User is false.
Returning a primitive is ignored
Numbers, strings, booleans and other primitives cannot overwrite this:
class User {
constructor(name) {
this.name = name;
return 42;
}
}
const u = new User("Tim");
console.log(u); // User { name: "Tim" }There is no error, the value is simply discarded. The same goes for null: it is a primitive, so it is ignored too, while return undefined changes nothing by definition.
Why this exists: Singleton and factories
The ability to return your own object is there so that a constructor can hand back something special. The classic example is a Singleton, one shared instance for every call:
class Database {
constructor() {
if (Database.instance) {
return Database.instance; // always hands back the same object
}
this.connected = true;
Database.instance = this;
}
}
const a = new Database();
const b = new Database();
console.log(a === b); // truereturn this is not needed here because the engine does it anyway, while return Database.instance substitutes the existing object for the new instance.
A constructor that returns different objects depending on its input works the same way:
class Example {
constructor(x) {
this.x = x;
if (x > 10) return { value: "too large" };
}
}
console.log(new Example(5)); // Example { x: 5 }
console.log(new Example(20)); // { value: "too large" }return in derived classes
The rule is the same with and without extends: a returned object replaces this.
class A {}
class B extends A {
constructor() {
super();
return { name: "replacement" };
}
}
const b = new B();
console.log(b); // { name: "replacement" }But super() stays mandatory even when you return your own object:
class A {}
class B extends A {
constructor() {
// error, even though there is a return
return { test: 1 };
}
}
new B(); // ReferenceError: Must call super constructor before returning from derived constructorAll the rules in one place:
| Scenario | What comes back |
|---|---|
No return | this, the created instance |
return this | the same instance |
return { ... } | that object instead of this |
return 42 / "hi" / true | ignored, this is returned |
Derived class without super() | an error: super() must precede return |
Common mistakes
- Returning an object literal "for convenience" and then being surprised that
instanceofand the class methods are gone: the substituted object has no class prototype. - Expecting
return 42to makenew User()a number. Primitives from a constructor are ignored with no warning at all. - An early
returnin a derived constructor beforesuper(). That is aReferenceError, not a quiet exit. - A Singleton written as
return Database.instancewith heavy initialisation happening before that check: the work is done again and then thrown away. - Confusion around
null: it is a primitive, sonewyieldsthis, notnull.
Short Answer
Interview readyA concise answer to help you respond confidently on this topic during an interview.