skip to content

In JavaScript, what happens when you invoke a class constructor without `new` — for example `Point(1, 2)` where `Point` was declared with the `class` keyword — and how did the old constructor-function style behave in the same situation?

level: juniorimportance: should knowfreq 58%

answer

  1. the call is rejected, not mis-run
  2. nothing in the body executes
  3. call and apply are refused too
  4. TypeError: cannot be invoked without 'new'

basics

~20 s

It throws a TypeError immediately: class constructors cannot be invoked without new, and the throw happens before any constructor body runs. An old-style constructor function instead ran normally, with this bound to undefined or the global object.

solid answer

~50 s

A class constructor throws a `TypeError` — "Class constructor Point cannot be invoked without 'new'" — and it throws before a single line of the constructor body executes. The check is on the function object itself, so it also rejects `Point.call(obj)`, `Point.apply(obj, args)`, `Reflect.apply(Point, ...)`, and passing the class as a callback such as `ids.map(User)`. Only `new Point(...)` and `Reflect.construct(Point, args)` are accepted. A pre-class constructor function had no such protection: `Point(1, 2)` just ran, with `this` being `undefined` in strict mode or the global object in sloppy mode, so it silently wrote globals and returned `undefined` — which is why defensive code used to start with `if (!(this instanceof Point)) return new Point(...)`. With classes that guard is dead code; if you want new-less construction, expose a static factory or a plain wrapper function.

code

javascript · 13 lines
javascript
class Point {
  constructor(x, y) { console.log('body ran'); this.x = x; this.y = y; }
  static of(x, y) { return new Point(x, y); }
}

try { Point(1, 2); } catch (e) { console.log(e.constructor.name, e.message); }
try { Point.call({}, 1, 2); } catch (e) { console.log('call() also throws'); }

console.log(Point.of(1, 2));                    // works
console.log([[1, 2]].map(([x, y]) => new Point(x, y)));

function Legacy(x) { this.x = x; }
console.log(Legacy(1));  // undefined - no error, no instance

go deeper

for a junior

Recall the exact outcome — a TypeError at the call site saying the class constructor cannot be invoked without new — and that an old-style function would have run instead.

for a middle

Explain that the rejection happens on the call itself, so the body never starts, and that call, apply, bound invocation and callback use are all refused while Reflect.construct is not.

for a senior

Show the migration angle: converting a constructor function to a class breaks callers that relied on new-less invocation or on borrowing the constructor with .call, and neither failure appears until that path executes.

for a principal

Frame it as API design — decide up front whether construction is public at all, and prefer static factories that can validate, cache or return a subtype over exposing a raw constructor.

## What the language does A function created by `class` syntax is marked as a class constructor. When you call it as a function rather than constructing it, the call is rejected outright: ```js class Point { constructor(x, y) { console.log('running'); this.x = x; this.y = y; } } Point(1, 2); // TypeError: Class constructor Point cannot be invoked without 'new' ``` Nothing is logged: the rejection happens when the function is called, before the body is entered. That is an important detail — you cannot "catch" the mistake inside the constructor, because the constructor never starts. It is also why `new.target` is not a workaround here. `new.target` is `undefined` in a plain call and the class constructor when constructed, but for a class you never get the chance to read it in the new-less case. `new.target` is still useful for other purposes, such as detecting whether an instance is being created directly or through a subclass. ## Everything that counts as "calling" The restriction is broader than the obvious `Point(1, 2)`: ```js Point.call({}, 1, 2); // TypeError Point.apply({}, [1, 2]); // TypeError const f = Point.bind(null); f(); // TypeError (bind is fine; invoking the bound function is not) Reflect.apply(Point, {}, []); // TypeError [1, 2].map(Point); // TypeError — map calls the callback ``` Construction paths still work: `new Point(1, 2)` and `Reflect.construct(Point, [1, 2])`. `Object.create(Point.prototype)` also works, but it does not run the constructor at all, so it produces an object with the right prototype and none of the constructor's initialization — a useful trick for deserialization, and a trap if you expect initialized state. A related consequence: a class constructor cannot be borrowed to initialize an unrelated object. The old `Parent.call(this, args)` idiom for reusing initialization logic simply does not work when `Parent` is a class. ## How the pre-class pattern behaved ```js function Point(x, y) { this.x = x; this.y = y; } const p = Point(1, 2); // no error ``` In sloppy mode, `this` inside that call is the global object, so `this.x = x` creates a global `x`; the expression evaluates to `undefined`, and the caller ends up with `undefined` instead of an object, usually failing much later and far from the cause. In strict mode, `this` is `undefined` and you get a `TypeError: Cannot set property 'x' of undefined` — better, but still a run-time surprise from inside the body. Because that was so easy to do by accident, library code commonly opened with a self-correcting guard: ```js function Point(x, y) { if (!(this instanceof Point)) return new Point(x, y); this.x = x; this.y = y; } ``` Some APIs kept that deliberately so callers could write `Point(1, 2)` as a shorthand. Class syntax removed the choice: the failure is now immediate, loud, and at the call site. ## What to do when you want new-less creation Since the class itself refuses, put the ergonomics beside it: ```js class Point { constructor(x, y) { this.x = x; this.y = y; } static of(x, y) { return new Point(x, y); } } const make = (x, y) => new Point(x, y); [[1, 2], [3, 4]].map(([x, y]) => new Point(x, y)); // callbacks need a wrapper ``` A static factory also gives you a place for validation, caching, or returning a subclass — things a constructor cannot do cleanly. ## Why the restriction exists A class constructor may be part of a hierarchy, and the initialization protocol for derived classes depends on being constructed rather than merely called; a plain call cannot satisfy it. Making the failure unconditional keeps the model simple and turns a whole category of silent global-leaking bugs into an immediate error at the exact line that caused it. Built-in constructors such as `Map` and `Set` behave the same way — `Map()` throws "Constructor Map requires 'new'" — while a few legacy built-ins keep special new-less behaviour for historical reasons, `Date()` returning a string being the classic example.

  • Can you make a class usable as an array callback such as `ids.map(User)`?
    Not directly — `map` calls the callback, and a class constructor rejects any call. Wrap it: `ids.map(id => new User(id))`, or add `static from(id) { return new User(id) }` and pass `User.from`, remembering that a detached static still needs `this` handled if it references the class by `this`.
  • Does `Object.create(Point.prototype)` give you a usable Point instance?
    It gives you an object whose prototype is `Point.prototype`, so `instanceof Point` is true and methods resolve — but the constructor never runs, so none of the instance state exists. It is useful for rehydrating a plain object from storage, and a bug if you assumed initialization happened.

saying these in an interview costs you the question

  • Says the constructor body runs with this as the global object
  • Thinks a new.target guard inside the constructor can rescue the call
  • Believes only the bare call form is blocked, not call/apply
  • Claims Object.create(C.prototype) runs the constructor
  • Assumes every built-in constructor throws without new

context