Chapter 5.2☕ 19 min read

Arrow vs Regular Functions: Every Single Difference

Arrow ka apna this nahi hota — yehi uski superpower hai aur yehi uski kamzori.

01Syntax — Concise vs Verbose

Arrow functions appeared in ES6 as a compact syntax. But the differences go way beyond just "less typing" — they fundamentally change how this, arguments, and construction work.

// Implicit return — concise
const double = x => x * 2;
const add = (a, b) => a + b;
const getObj = () => (({ name: 'Sai' })); // parens for object literal

// Regular function — explicit
function doubleRegular(x) {
  return x * 2;
}

// Returning object literal — common trap!
// const wrong = () => { name: 'Sai' }; // Returns undefined!
// 'name:' is a label, not a property key
// const right = () => ({ name: 'Sai' }); // Wrapping in () fixes it

// Arrow CANNOT be a generator
// function* gen() { yield 1; } // OK
// const genArrow = *() => { yield 1; }; // SyntaxError!
Object literal return trap: When an arrow uses concise body (no curly braces), a bare { is parsed as a block statement, not an object literal. () => { name: 'Sai' } returns undefinedname: is a label, not a property. Wrap in parentheses: () => ({ name: 'Sai' }) forces expression evaluation.

Key syntax rules:

• Single parameter: no parentheses needed — x => x * 2

• Zero or multiple parameters: parentheses required — () => 42, (a, b) => a + b

• Arrow functions are always expressions — they cannot be hoisted like function declarations

• Arrow functions are always anonymous (though .name can be inferred from the variable)

02No this Binding: Lexical this

Arrow functions do NOT have their own this binding. This is the single most important difference — and the one that trips up the most developers.

Inside an arrow function, this is inherited from the enclosing Lexical Environment. It's captured at definition time, not resolved at call time.

function Timer() {
  this.seconds = 0;
  
  // Regular function — 'this' is lost in setInterval
  // setInterval(function() { this.seconds++; }, 1000); // NaN or error!
  
  // Arrow function — 'this' inherited from Timer scope
  setInterval(() => {
    this.seconds++; // 'this' is the Timer instance!
  }, 1000);
}

const timer = new Timer();
setTimeout(() => console.log(timer.seconds), 2500); // 2

// .call() cannot override arrow's this
const arrow = () => this;
console.log(arrow.call({ x: 1 })); // window/undefined, NOT { x: 1 }
How V8 implements this: When a regular function is called, the bytecode MustLoadThis reads the receiver from the call site. For arrow functions, V8 replaces this with a direct context slot access — exactly like accessing a closed-over variable. There is no runtime this-resolution at all. The this value is baked into the arrow's closure at creation time.
03No arguments, No new, No super

Arrow functions are missing several features that regular functions have. These aren't bugs — they're intentional design decisions.

No arguments object: Arrow functions don't have their own arguments binding. If you reference arguments inside an arrow, it looks up the scope chain to find the nearest enclosing function's arguments — or throws a ReferenceError.

// No 'arguments' object
const regular = function() {
  console.log(arguments[0]); // Works
};
regular('Hello'); // 'Hello'

const arrow = () => {
  // console.log(arguments[0]); // ReferenceError or outer arguments!
};
arrow('Hello');

// Correct way for arrows: Rest parameters
const arrowRest = (...args) => {
  console.log(args[0]); // 'Hello'
};
arrowRest('Hello');

Cannot use new: Arrow functions have no [[Construct]] internal method and no .prototype property.

const F = () => {};
// new F(); // TypeError: F is not a constructor

function G() {}
new G(); // OK
console.log(G.prototype); // {}
console.log(F.prototype); // undefined
Why no [[Construct]]? V8 optimizes arrow functions differently. Since they can never be constructors, V8 skips creating the prototype object and doesn't generate the construct stub bytecode. This makes arrow functions slightly faster to create — but you sacrifice the ability to use them with new.
04When to Use Arrows (And When NOT To)

Knowing when to use arrows vs regular functions is more important than knowing the syntax. Here are the practical rules:

✅ USE arrows for: Short callbacks (map, filter, reduce), preserving this in nested functions, one-liner expressions.

❌ DO NOT use arrows for: Object methods, Prototype methods, Event handlers that need this = element, Functions that need arguments, Constructors.

// ❌ BAD: Arrow as object method
const user = {
  name: 'Sai',
  greet: () => 'Hello, ' + this.name // 'this' is window/undefined!
};
console.log(user.greet()); // 'Hello, undefined'

// ✅ GOOD: Shorthand method (regular function)
const userGood = {
  name: 'Sai',
  greet() { return 'Hello, ' + this.name; } // 'this' is userGood
};
console.log(userGood.greet()); // 'Hello, Sai'

// ❌ BAD: Arrow as event handler needing 'this'
const button = document.createElement('button');
// button.addEventListener('click', () => {
//   this.classList.toggle('active'); // 'this' is NOT the button!
// });

// ✅ GOOD: Regular function for event 'this'
// button.addEventListener('click', function() {
//   this.classList.toggle('active'); // 'this' IS the button
// });
Class fields with arrows: handleClick = () => { ... } is popular in React to auto-bind this. But every instance gets its OWN copy of the arrow function, instead of sharing one on the prototype. For a component with 1000 instances, that's 1000 extra function objects in memory.
05Every Difference Summarized

Here's the complete comparison — memorize this for interviews:

Feature              Arrow          Regular
─────────────────────────────────────────────
this binding         Lexical        Dynamic
arguments object     No             Yes
new keyword          No             Yes
.prototype property  No             Yes
yield (generator)   No             Yes
super                Enclosing      Method only
Duplicate params     No (strict)    Yes (sloppy)
Hoisting             No (expr)      Yes (decl)
Constructor          No             Yes

Rule of thumb: If you need this, arguments, or new — use a regular function. Otherwise, arrow is fine.

// Arrow is perfect for pure callbacks
const numbers = [1, 2, 3, 4];
const doubled = numbers.map(n => n * 2); // Clean, no 'this' needed

// Regular is needed for methods
const calculator = {
  value: 0,
  add(n) { this.value += n; return this; }, // Needs 'this'
  double() { return this.value * 2; }
};

Lo kar liya — Key Points:

  • ✅ Arrow functions do not have their own this — they inherit it from the enclosing lexical scope
  • .call(), .apply(), and .bind() cannot change the this of an arrow function
  • ✅ Arrow functions have no arguments object — use rest parameters (...args) instead
  • ✅ Arrow functions cannot be used as constructors (no new, no .prototype)
  • ✅ Never use arrows for object methods or event handlers that need this to be the receiver
  • ✅ V8 implements arrow this as a context slot access (like a closed-over variable), not dynamic lookup
Course Search
Search across all chapters & stages
📖

Search the course

Type any topic — branching, stash, rebase, hooks — and jump straight to that chapter.

merge branchesgit stashundo commitrebase