The new Operator & Constructors Interview Questions & Answers

25 questions Updated 2026-06-18

JavaScript constructor interview questions — what the new operator does, constructor functions, returning values, new.target, and common new-related bugs.

Read the in-depth guideThe new Operator & Constructor Functions in JavaScript — How Object Creation Really Works

new Fn(args) performs four steps:

  1. Creates a fresh empty object.
  2. Sets that object's prototype to Fn.prototype.
  3. Calls Fn with this bound to the new object.
  4. Returns the new object — unless Fn explicitly returns its own object.
function User(name) { this.name = name }
const u = new User('Ada')
// roughly:
// const u = Object.create(User.prototype)
// User.call(u, 'Ada')

Those four steps are the whole "magic" of new. Understanding them explains this, the prototype link, and the return-value rules below.

A regular function intended to be called with new to create objects. By convention its name is capitalized. Inside, this is the new instance.

function Point(x, y) {
  this.x = x
  this.y = y
}
const p = new Point(1, 2)   // { x: 1, y: 2 }

There's nothing special about the function itself — any function can be a constructor. The capitalization is a signal to callers that it must be invoked with new. Classes are the modern replacement.

Without new, the function runs as a plain call: this is undefined (strict) or the global object (sloppy), so it doesn't create an instance — and in sloppy mode it leaks properties onto the global object.

function User(name) { this.name = name }
const u = User('Ada')   // no new
u                       // undefined (function returns nothing)
// sloppy mode: created a global `name`!

Guards: use a class (which throws if called without new), or check new.target/instanceof and re-invoke with new. This bug is exactly why classes enforce new.

If a constructor returns an object, that object replaces the newly created this. If it returns a primitive (or nothing), the primitive is ignored and this is returned as usual.

function A() { this.x = 1; return { y: 2 } }
new A()   // { y: 2 } — object return overrides `this`

function B() { this.x = 1; return 42 }
new B()   // { x: 1 } — primitive return ignored

This "return an object to override" behavior enables some factory tricks but is usually a footgun — most constructors return nothing.

A meta-property that is the constructor invoked with new, or undefined for a normal call. It lets a function detect how it was called.

function User(name) {
  if (!new.target) throw new Error('User must be called with new')
  this.name = name
}
User('Ada')      // throws
new User('Ada')  //

In a class hierarchy, new.target is the actual subclass being constructed — useful for abstract base classes and for factory logic that varies by concrete type.

new Fn() sets the instance's prototype to Fn.prototype, so the instance inherits everything defined there.

function Dog() {}
Dog.prototype.bark = () => 'woof'
const d = new Dog()
Object.getPrototypeOf(d) === Dog.prototype  // true
d.bark()                                      // 'woof' (inherited)

That's why methods go on Fn.prototype — every new instance shares them via the prototype link. Reassigning Fn.prototype after creating an instance doesn't change that instance's prototype.

  • A constructor is used with new, relies on this, and links instances to a shared prototype.
  • A factory is a normal function that returns an object (no new, no this), often using closures for privacy and composition for behavior.
function User(name) { this.name = name }       // constructor -> new User()
function createUser(name) { return { name } }   // factory -> createUser()

Factories avoid new/this pitfalls and offer closure-based privacy; constructors/classes share methods via the prototype (more memory-efficient for many instances). Both are valid.

When called with new, this is the brand-new instance being created, so assignments to this.x become the object's own properties.

function Account(balance) {
  this.balance = balance       // own property of the new instance
  this.deposit = function (n) { this.balance += n }
}

Called without new, this follows normal rules (undefined/global), which is the source of the "forgot new" bug. Inside a class constructor, this is the instance (and is undefined until super() runs in a subclass).

Use instance.constructor (inherited from the prototype) or instanceof.

function User() {}
const u = new User()
u.constructor === User        // true
u.constructor.name            // 'User'
u instanceof User             // true

constructor is informational and can be wrong if the prototype was replaced without restoring it. For reliable type checks, prefer instanceof (or duck-typing), not the constructor property.

JavaScript provides constructors for its core types: Object, Array, Function, Date, RegExp, Map, Set, Promise, Error, and the wrapper constructors String/Number/Boolean.

new Date()
new Map()
new Error('boom')
new Array(3)        // [ <3 empty items> ] — quirky

Most are used with new, though some (like Array, Object) also work without it. Avoid new String/Number/Boolean — they create wrapper objects that break === and are always truthy.

new Array(n) with a single number creates an empty array of length n (holes), not an array containing n. With multiple args it creates an array of those elements.

new Array(3)        // [ <3 empty items> ] — length 3, no values
new Array(1, 2, 3)  // [1, 2, 3]
Array.of(3)         // [3] — the unambiguous alternative
Array.from({length: 3}, (_, i) => i)  // [0, 1, 2]

This inconsistency is why Array.of exists and why array literals [] are preferred. The "empty items" are holes, which behave oddly with iteration.

obj instanceof Ctor is true when Ctor.prototype is in obj's prototype chain — i.e. obj was (effectively) built by Ctor or a subclass.

const d = new Date()
d instanceof Date    // true
d instanceof Object  // true (Object.prototype is in the chain)

It checks the prototype chain, not the literal constructor used, so it sees inheritance. It can be fooled by reassigned prototypes and fails across realms — use Array.isArray / Object.prototype.toString for those cases.

In a derived class, the instance (this) is created by the parent constructor, so you must call super() first to initialize it. Accessing this before super() throws a ReferenceError.

class Animal { constructor(name) { this.name = name } }
class Dog extends Animal {
  constructor(name) {
    this.bark = true     // ReferenceError — before super()
    super(name)
    this.bark = true     //
  }
}

This is a real difference from constructor functions, where this exists immediately. The rule guarantees the parent has set up the object before the child touches it.

Cache the instance and return it on subsequent new calls — leveraging the "return an object overrides this" rule.

class Config {
  constructor() {
    if (Config.instance) return Config.instance
    Config.instance = this
  }
}
new Config() === new Config()   // true — same instance

Because returning an object from a constructor replaces the new this, the second new gets the cached instance. (A module-level singleton or a frozen object is often simpler.)

Put methods on the prototype (shared, one copy); put per-instance data in the constructor (on this).

function User(name) {
  this.name = name                    // instance data
}
User.prototype.greet = function () {   // shared method
  return `Hi, ${this.name}`
}

Defining methods inside the constructor (this.greet = function(){}) creates a new function per instance — wasteful. Classes handle this correctly by placing methods on the prototype automatically.

new overrides a bound this — the instance wins. Bound arguments are still applied.

function Point(x, y) { this.x = x; this.y = y }
const Bound = Point.bind({ ignored: true }, 10)  // pre-bind x = 10
const p = new Bound(20)
p.x  // 10 (bound arg kept)
p.y  // 20
// the bound `this` ({ ignored: true }) is ignored — p is a fresh Point

This reflects precedence: new beats explicit binding. It's the one case where bind's "permanent" this doesn't hold.

Function constructors are hoisted (usable before their line); class constructors are hoisted but in the temporal dead zone, so they're not usable early.

new Fn()              // works — function declaration hoisted
function Fn() {}

new Cls()             // ReferenceError — TDZ
class Cls {}

So switching from a constructor function to a class can break code that instantiated it above its declaration. Always declare classes before use.

Check new.target against the base and throw, so only subclasses can be created.

class Shape {
  constructor() {
    if (new.target === Shape) throw new Error('Shape is abstract')
  }
  area() { throw new Error('not implemented') }
}
class Circle extends Shape { area() { return 1 } }
new Shape()   // throws
new Circle()  //

JavaScript has no built-in abstract keyword, so new.target (plus throwing from unimplemented methods) is the idiom for abstract base classes.

The functional form of new — it invokes a constructor and optionally lets you specify a different prototype (newTarget), useful in metaprogramming and for subclassing built-ins.

const obj = Reflect.construct(Array, [1, 2, 3])   // like new Array(1,2,3)
// third arg sets new.target / the prototype to use:
Reflect.construct(Base, args, Derived)

It's how you invoke a constructor dynamically (with an args array) and control the resulting prototype — the building block frameworks use for advanced instantiation.

Call the parent with Parent.call(this, args) so the parent initializes the child's instance fields.

function Animal(name) { this.name = name }
function Dog(name, breed) {
  Animal.call(this, name)   // parent sets this.name
  this.breed = breed
}

This is the pre-class way to "chain" constructors. With classes, super(name) does the same thing, and is required before using this in a subclass.

'function' — both constructor functions and classes are functions under the hood.

function Fn() {}
class Cls {}
typeof Fn    // 'function'
typeof Cls   // 'function'

Classes are special functions (strict mode, non-enumerable methods, must be called with new), but typeof doesn't distinguish them. To tell a class from a regular function, you'd inspect the source or try calling without new.

Keep constructors focused on initialization. Heavy work — network calls, DOM access, throwing on async failures — makes objects hard to create, test, and reason about.

// side-effectful constructor
class User { constructor(id) { this.data = fetch(`/u/${id}`) } }

// construct simply, do work in a method/static factory
class User {
  constructor(data) { this.data = data }
  static async load(id) { return new User(await (await fetch(`/u/${id}`)).json()) }
}

A common pattern is a static async factory (User.load) that does the I/O and then constructs a plain instance — constructors can't be async.

{} is shorter, clearer, and slightly faster than new Object(), with no downside. The same goes for [] over new Array() and /regex/ over new RegExp() for static patterns.

const a = {}            // idiomatic
const b = new Object()  // verbose, no benefit

Use new for types that genuinely need a constructor (Date, Map, Set, Promise, custom classes), and literals for the primitives-with-literals (objects, arrays, regexes).

Have each method return this, so calls can be chained on the instance.

class QueryBuilder {
  constructor() { this.parts = [] }
  where(c) { this.parts.push(c); return this }
  order(o) { this.parts.push(o); return this }
  build() { return this.parts.join(' ') }
}
new QueryBuilder().where('a=1').order('b').build()

Returning this (the instance) from methods is the builder/fluent pattern. It works because methods are called on the instance, so this is consistent across the chain.

Use closures: declare variables inside the constructor and expose only methods that close over them — the variables aren't accessible as properties.

function Counter() {
  let count = 0                          // private
  this.increment = () => ++count
  this.value = () => count
}
const c = new Counter()
c.increment()
c.value()   // 1
c.count     // undefined — truly private

The trade-off: these methods are per-instance (closures), not shared on the prototype. Modern classes offer #private fields for shared-method privacy without closures.

Practice tests are coming soon

Get notified when interactive mock interviews and quizzes launch.