skip to content

Besides having no `this` of their own, what else do JavaScript arrow functions lack compared with ordinary functions, and what happens if you call `new` on one?

level: middleimportance: should knowfreq 62%

answer

  1. four bindings never created
  2. rest parameters instead of arguments
  3. no prototype object at all
  4. new fails before the body runs
  5. no generator arrow syntax exists

basics

~20 s

Arrow functions have no own arguments, no super, no new.target, and no prototype property, and they are not constructors: new on an arrow throws a TypeError. They also cannot be generators. All four missing bindings resolve lexically instead.

solid answer

~40 s

An arrow is deliberately a stripped-down function object. Four bindings that an ordinary function creates on every call — `this`, `arguments`, `super`, and `new.target` — are simply absent, so each one resolves outward to the enclosing ordinary function, method, or class field initializer. An arrow also has no `prototype` property (`typeof (() => {}).prototype` is `'undefined'`) and no `[[Construct]]` internal method, so `new (() => {})` throws `TypeError: ... is not a constructor`. It cannot be declared as a generator either — there is no `async *` or `*` arrow form — though `async () => {}` is fine. The practical consequences: use rest parameters `(...args) =>` when you need the argument list, and never reach for an arrow where a constructor, a prototype method, or a generator is wanted.

code

javascript · 18 lines
javascript
const arrow = () => {};

console.log(typeof arrow.prototype); // 'undefined'

try {
  new arrow();
} catch (e) {
  console.log(e.constructor.name);   // 'TypeError'
}

function outer() {
  const inner = () => arguments[0];
  return inner();
}
console.log(outer('from outer'));    // 'from outer'

const sum = (...nums) => nums.length;
console.log(sum(1, 2, 3));           // 3

go deeper

for a junior

Remember the short list — no own this, no arguments, no prototype, cannot be used with new — and reach for rest parameters when you need the argument list.

for a middle

Explain that these bindings are never created rather than set to a default, so each resolves lexically to the enclosing ordinary function, and that new fails because the function object has no [[Construct]].

for a senior

Predict concrete refactor damage: constructor functions rewritten as arrows, legacy code depending on arguments, and prototype augmentation that now throws — and say which failures are loud versus silent.

for a principal

Frame the design rationale and the guardrails: arrows are the cheap callback form precisely because they allocate no prototype and no per-call bindings, and lint rules plus class syntax should keep constructor-shaped code out of arrow syntax entirely.

## What an ordinary function creates per call When an ordinary function is invoked, the engine builds a function environment record holding several bindings that only functions can supply: - `this` — the receiver, decided by the call form. - `arguments` — an array-like object of the actual arguments passed. - `super` — available in methods with a home object, for property and constructor access. - `new.target` — the constructor invoked with `new`, otherwise `undefined`. An arrow function's environment record creates **none** of these. The spec models this as the arrow having a `[[ThisMode]]` of `lexical`. Each of the four therefore resolves like an ordinary free variable, walking outward until it finds a scope that does define it. ## `arguments` ```javascript function outer() { const inner = () => arguments[0]; return inner(); } outer('from outer'); // 'from outer' ``` The arrow did not get its own `arguments`; it read `outer`'s. That is occasionally useful and frequently a trap — someone writes a standalone arrow, reaches for `arguments`, and gets a ReferenceError (no enclosing function) or, worse, a different function's arguments. The modern answer is rest parameters, which work identically in both function kinds and give a real array: ```javascript const sum = (...nums) => nums.reduce((a, b) => a + b, 0); ``` ## No `prototype`, no `[[Construct]]` An ordinary function declaration is born with a `prototype` object used to seed instances created via `new`. An arrow has no such property, and the function object lacks the `[[Construct]]` internal method entirely: ```javascript const Arrow = () => {}; typeof Arrow.prototype; // 'undefined' new Arrow(); // TypeError: Arrow is not a constructor ``` This is not a runtime check inside the body — the call fails before any body runs. Class methods and methods defined with shorthand syntax are also non-constructors, so the arrow is not unique here, but arrows are the case candidates meet first. ## No generator form There is no `*() => {}` syntax. If you need `yield`, you need `function*` or a shorthand generator method. `async () => {}` is legal and common; `async` arrows still lack all four bindings above. ## `super` and `new.target` Both resolve lexically too. Inside a class method, an arrow can use `super.method()` because it inherits the method's home object: ```javascript class Sub extends Base { run() { const go = () => super.run(); // legal: super comes from run() return go(); } } ``` The same arrow written at module top level is a syntax error, because there is no enclosing home object for `super` to bind against. `new.target` follows the same pattern: an arrow inside a constructor sees the constructor's `new.target`, which is how a helper arrow can tell whether the surrounding function was called with `new`. ## What arrows keep Arrows are still ordinary function *objects*. They have `length` and `name`, they are `typeof 'function'`, they inherit from `Function.prototype`, and you can call `call`, `apply`, and `bind` on them — those methods still forward arguments; only the thisArg is inert. They can close over variables like any function, and they can be `async`. ## Why the language was designed this way Every removed feature is a per-call allocation or a source of confusion in callback-heavy code. Dropping `this` and `arguments` means a nested arrow reads like the surrounding code rather than shadowing it; dropping `prototype` and `[[Construct]]` means the tiny throwaway function you passed to `map` cannot be mistaken for a class. Engines can also allocate arrows more cheaply because there is no `prototype` object to create. ## What an interviewer is testing Not memorization of a list. The signal is whether you can predict a failure: someone converts a constructor function to arrow syntax during a refactor and every `new` site breaks; someone converts a legacy function that uses `arguments` and it silently starts reading an outer function's arguments; someone tries `Arrow.prototype.helper = ...` and quietly attaches a property to `undefined`, throwing a TypeError. Name the mechanism and the failure together.

  • A refactor converts `function Point(x, y) { this.x = x; }` to `const Point = (x, y) => { this.x = x; }`. What breaks and when?
    Every `new Point(...)` call site throws `TypeError: Point is not a constructor`, and it throws before the body runs, so no partially built object escapes. Anything hanging off `Point.prototype` also breaks, since arrows have no `prototype` property — the assignment itself throws. The failure is immediate and total rather than subtle, which is the one mercy here.
  • If arrows have no `arguments`, how do you write a variadic arrow function?
    Use rest parameters: `const f = (...args) => args.length`. Rest gives a real `Array`, so `map`, `filter` and destructuring work directly, unlike the array-like `arguments` object. It also works identically in ordinary functions, which is why `arguments` is legacy surface in modern code.
  • Can an arrow function use `super`?
    Yes, when it is written inside something that has a home object — a class method, a class field initializer, or an object-literal shorthand method. The arrow inherits that home object and `super.foo()` resolves against it. Written anywhere without an enclosing home object, `super` is a syntax error, because the arrow supplies none of its own.

saying these in an interview costs you the question

  • Says arrows have their own arguments object
  • Thinks new on an arrow returns a plain object
  • Claims arrow.prototype exists but is empty
  • Believes there is an async generator arrow form
  • Says bind and call cannot be used on arrows at all

context