Difference between arrow functions and regular JavaScript functions
Arrow functions inherit `this`/`arguments` lexically, can't be used with `new`, and have no `prototype`. Regular functions get their own `this` based on the call site and can be constructors.
Arrow functions are not "just a shorter syntax" for regular functions — they are a semantically different function form. Treating them as interchangeable is the most common source of this-related bugs in modern JavaScript. Understanding seven differences, and when each matters, is what an interviewer is checking for.
1. this — lexical, not dynamic. This is the headline. Regular functions resolve this at the call site (one of four binding rules: new, explicit, implicit, default). Arrow functions don't have their own this at all — lookup falls through to the enclosing function's this at the moment the arrow was defined. As a consequence: .call(ctx, ...), .apply(ctx, ...), and .bind(ctx) cannot change an arrow's this. They silently accept the context argument and ignore it. This is exactly why arrows are perfect for callbacks where you want to inherit this from the enclosing method, and exactly why they're wrong for object methods that should be called with method-binding syntax.
2. arguments — not present. Regular functions get the magic arguments array-like inside their body; arrows don't. Use rest parameters instead: const f = (...args) => args.length. This is by design — arguments is treated as legacy in modern JS, and rest parameters are properly typed in TypeScript.
3. new — not constructible. new (() => {}) throws TypeError: ... is not a constructor. Arrows don't have the internal [[Construct]] slot. So they can't be used as constructors and can't have an implicit prototype.
4. prototype — not present. A regular function has a .prototype property (used by new to set up the new instance's prototype chain). Arrows don't, which is why class Foo { method = () => {} } (an arrow class field) is per-instance allocation, not a prototype method.
5. Hoisting and TDZ. Function declarations (function foo() {}) are hoisted whole — name and body both available before the line. Arrow functions are always expressions assigned to a binding (const foo = () => {}), so they obey TDZ: referencing foo before the assignment line throws ReferenceError. Don't expect "function defined later in the file" semantics with arrows.
6. Generators — not supported. There is no function arrow form. If you need a generator, use a regular function: function gen() { yield 1; }.
7. Implicit return + brevity. Single-expression arrows return the expression without return: const sq = x => x * x. Wrapping the body in {} makes it a block, so you need explicit return. Returning an object literal needs parentheses: x => ({ y: x }) (otherwise { y: x } is parsed as a block).
Practical heuristic: when to use which.
- Use arrows for: callbacks passed to
map/filter/reduce;setTimeout/setIntervalcallbacks inside class or object methods (sothisstays bound); React render-prop functions; event handlers in functional components. - Use regular functions for: methods on objects/classes that need their own
thisand benefit from prototype sharing; constructors and class definitions; generators; functions where you want hoisting semantics.
Common pitfalls and patterns:
const obj = { name: 'X', greet: () => this.name }— captures the enclosing scope'sthis(undefinedin modules), notobj. Usegreet() { return this.name; }for a true method.- React class arrow fields (
onClick = () => this.handle()) — bindthiscorrectly but allocate one closure per instance. Prototype methods are shared; the trade-off is whether you need stable identity (memoization) more than memory. setInterval(function () { this.count++; }, 1000)inside a class —thisis global. Either use an arrow, or.bind(this).arguments.lengthin an arrow — TypeError. Use...args.length.
TypeScript-specific note. TypeScript handles this typing differently for arrows: this is inferred from the enclosing scope. Methods can declare a this parameter (function foo(this: User)) for type-checking; arrows can't.
Interview-ready one-liner. "Arrow functions are expressions that lexically capture this and arguments from their enclosing scope, can't be used with new, and don't have a prototype. They're best for callbacks; they're wrong for object methods or constructors. The most common bug is using an arrow where the caller expected this to be re-bindable."
Code
Follow-up questions
- •Why can't arrow functions be used as constructors?
- •Implement a polyfill for an arrow-style 'thisless' helper.
- •Why do React class fields written as arrow functions cost more memory than prototype methods?
Common mistakes
- •Using an arrow as an object method and expecting `this` to refer to the object.
- •Trying to access `arguments` inside an arrow function.
- •Defining a class method as an arrow class field for 'auto-binding' without realizing it allocates per instance.
Performance considerations
- •Arrow class fields allocate one function per instance; prototype methods allocate one shared per class. Matters at thousands of instances.
Edge cases
- •An arrow returning an object literal needs parens: `() => ({ a: 1 })`. Without them, `{ a: 1 }` is a block.
- •`Function.prototype.bind` on an arrow is a no-op for `this`, but still applies partial args.
Real-world examples
- •React event handlers: `<button onClick={() => save(id)}>` — arrow keeps the component's `this`/closures and is the idiomatic pattern.