When to Avoid Using Arrow Functions in JavaScript

Learn when arrow functions are the wrong choice in JavaScript. Covers object methods, prototype methods, constructors, event handlers needing this, arguments object usage, and generator functions where regular functions are required.

JavaScriptbeginner
10 min read

Arrow functions are excellent for callbacks, inline functions, and short utilities. But they are not a universal replacement for regular functions. Their lexical this binding, lack of arguments object, and inability to serve as constructors make them the wrong choice in several common scenarios. Using an arrow function where a regular function is needed creates bugs that can be difficult to track down because the code looks correct but behaves unexpectedly.

This tutorial covers every situation where arrow functions cause problems and shows the correct alternative for each case.

Problem 1: Object Methods

Arrow functions inherit this from their enclosing scope. When used as object methods, this does not point to the object:

javascriptjavascript
// Bug: arrow function as object method
const user = {
  name: "Alice",
  greet: () => {
    return `Hello, I'm ${this.name}`; // 'this' is NOT the user object
  },
};
 
console.log(user.greet()); // "Hello, I'm undefined"

The arrow function's this comes from the surrounding scope (likely the global object or undefined in modules), not from the user object.

Fix: Use Method Shorthand or Regular Function

javascriptjavascript
// Correct: method shorthand
const user = {
  name: "Alice",
  greet() {
    return `Hello, I'm ${this.name}`;
  },
};
 
console.log(user.greet()); // "Hello, I'm Alice"
javascriptjavascript
// Also correct: regular function expression
const user = {
  name: "Alice",
  greet: function () {
    return `Hello, I'm ${this.name}`;
  },
};
 
console.log(user.greet()); // "Hello, I'm Alice"
Approachthis valueRecommended
Arrow functionEnclosing scopeNo
Method shorthandThe objectYes
Regular functionThe objectYes
Object Methods Must Be Regular Functions

Any method that needs to access the object via this must be a regular function or method shorthand. Arrow functions will always look up this from the outer scope, ignoring the object entirely.

Problem 2: Prototype Methods

The same this issue affects prototype methods:

javascriptjavascript
// Bug: arrow function on prototype
function Person(name) {
  this.name = name;
}
 
Person.prototype.greet = () => {
  return `Hi, I'm ${this.name}`; // 'this' is NOT the instance
};
 
const alice = new Person("Alice");
console.log(alice.greet()); // "Hi, I'm undefined"

Fix: Use Regular Function

javascriptjavascript
Person.prototype.greet = function () {
  return `Hi, I'm ${this.name}`;
};
 
const alice = new Person("Alice");
console.log(alice.greet()); // "Hi, I'm Alice"

Problem 3: Constructor Functions

Arrow functions cannot be used with new. They have no prototype property and no internal [[Construct]] method:

javascriptjavascript
// Bug: arrow function as constructor
const Car = (make, model) => {
  this.make = make;
  this.model = model;
};
 
// TypeError: Car is not a constructor
// const myCar = new Car("Toyota", "Camry");

Fix: Use Regular Function or Class

javascriptjavascript
// Regular constructor function
function Car(make, model) {
  this.make = make;
  this.model = model;
}
 
const myCar = new Car("Toyota", "Camry");
console.log(myCar.make); // "Toyota"
javascriptjavascript
// ES6 class (preferred in modern code)
class Car {
  constructor(make, model) {
    this.make = make;
    this.model = model;
  }
}
 
const myCar = new Car("Toyota", "Camry");
console.log(myCar.make); // "Toyota"

Problem 4: Event Handlers That Need this

In DOM event handlers, this refers to the element that triggered the event. Arrow functions break this behavior:

javascriptjavascript
// Bug: arrow loses the element reference
const button = document.getElementById("submit");
 
button.addEventListener("click", () => {
  console.log(this);           // Window (or undefined in modules)
  this.classList.add("active"); // Error or wrong element
});

Fix: Use Regular Function

javascriptjavascript
button.addEventListener("click", function () {
  console.log(this);           // <button id="submit">
  this.classList.add("active"); // correct element
});

When Arrow Functions ARE Fine in Event Handlers

If you access the element through the event parameter instead of this, arrows work perfectly:

javascriptjavascript
button.addEventListener("click", (event) => {
  console.log(event.target);            // <button id="submit">
  event.target.classList.add("active"); // correct, using event.target
});
Access methodArrow functionRegular function
thisWrong (enclosing scope)Correct (element)
event.targetCorrectCorrect
event.currentTargetCorrectCorrect

Problem 5: The arguments Object

Arrow functions do not have their own arguments object:

javascriptjavascript
// Bug: arguments is not available
const logArgs = () => {
  console.log(arguments); // ReferenceError (or inherits from outer function)
};
 
// logArgs(1, 2, 3); // Error

If an arrow function is inside a regular function, it inherits that function's arguments, which creates confusing behavior:

javascriptjavascript
function outer() {
  const inner = () => {
    console.log(arguments); // outer's arguments, not inner's
  };
  inner("ignored");
}
 
outer("hello", "world");
// Arguments ["hello", "world"] -- from outer, not inner

Fix: Use Rest Parameters or Regular Function

javascriptjavascript
// Rest parameters (preferred)
const logArgs = (...args) => {
  console.log(args); // [1, 2, 3]
};
 
logArgs(1, 2, 3);
javascriptjavascript
// Regular function (when arguments object is specifically needed)
const logArgs = function () {
  console.log(arguments); // Arguments [1, 2, 3]
};
 
logArgs(1, 2, 3);

Problem 6: Dynamic this with call, apply, bind

Arrow functions ignore call, apply, and bind because their this is permanently fixed:

javascriptjavascript
const greet = () => {
  return `Hello, ${this.name}`;
};
 
const user = { name: "Alice" };
 
// .call, .apply, .bind have no effect on arrow functions
console.log(greet.call(user));    // "Hello, undefined"
console.log(greet.apply(user));   // "Hello, undefined"
 
const bound = greet.bind(user);
console.log(bound());              // "Hello, undefined"

Fix: Use Regular Function

javascriptjavascript
const greet = function () {
  return `Hello, ${this.name}`;
};
 
const user = { name: "Alice" };
 
console.log(greet.call(user));  // "Hello, Alice"
console.log(greet.apply(user)); // "Hello, Alice"
 
const bound = greet.bind(user);
console.log(bound());            // "Hello, Alice"

Problem 7: Generator Functions

Arrow functions cannot be generators. There is no =>* syntax:

javascriptjavascript
// SyntaxError: not possible
// const gen = *() => { yield 1; };

Fix: Use Regular Generator Function

javascriptjavascript
function* counter(start, end) {
  for (let i = start; i <= end; i++) {
    yield i;
  }
}
 
for (const num of counter(1, 5)) {
  console.log(num); // 1, 2, 3, 4, 5
}

Complete Decision Table

SituationUse arrowUse regularWhy
Array callback (map, filter, reduce)YesOkayArrows are concise; this not needed
Promise .then() callbackYesOkayLexical this is usually desired
setTimeout/setInterval callbackYesWith cautionArrows preserve outer this
Object methodNoYesMethod needs this = the object
Prototype methodNoYesMethod needs this = the instance
Constructor functionNoYesArrows cannot be used with new
Class methodNoYesClass methods need dynamic this
Event handler needing thisNoYesthis should be the DOM element
Event handler using event.targetYesYesBoth work when not using this
Function using argumentsNoYesArrows have no arguments
Function called with .call()/.apply()NoYesArrows ignore explicit this
Generator functionNoYesNo arrow generator syntax exists
Simple Rule

If the function needs its own this, its own arguments, needs to be a constructor, or needs to be a generator, use a regular function. For everything else, arrow functions are a great default.

Common Mistake Patterns

Class Method Defined as Arrow in Object Literal

javascriptjavascript
// Bug in mixin/utility object
const loggerMixin = {
  log: () => {
    console.log(`[${this.name}] logged`); // this.name is undefined
  },
};
 
// Fix
const loggerMixin = {
  log() {
    console.log(`[${this.name}] logged`);
  },
};

Arrow in Class Method That Gets Passed as Callback

javascriptjavascript
class Counter {
  count = 0;
 
  // Arrow as class field: works, binds 'this' to instance
  increment = () => {
    this.count++;
  };
 
  // Regular method: 'this' lost when passed as callback
  decrement() {
    this.count--;
  }
}
 
const c = new Counter();
const btn = document.getElementById("up");
 
// Works: arrow field keeps 'this'
btn.addEventListener("click", c.increment);
 
// Bug: 'this' becomes the button element
btn.addEventListener("click", c.decrement);
 
// Fix: bind or wrap
btn.addEventListener("click", () => c.decrement());
btn.addEventListener("click", c.decrement.bind(c));

Arrow class fields are one situation where arrows intentionally bind this to the instance. This is a valid pattern in React class components and event handler setups.

Rune AI

Rune AI

Key Insights

  • Never use arrows for object methods: this will point to the enclosing scope, not the object
  • Arrows cannot be constructors: no new, no prototype property
  • Arrows ignore call/apply/bind: their this is permanently locked to the enclosing scope
  • Use rest parameters instead of arguments: (...args) works in arrows and produces a real array
  • Arrow class fields are an exception: they intentionally bind this to the instance for callback use
RunePowered by Rune AI

Frequently Asked Questions

Can I use arrow functions as class methods?

Regular class methods should use the standard method syntax. However, arrow functions as class fields (`increment = () => {}`) are a valid pattern when you need methods that always bind to the instance, such as event handlers in React class components. The tradeoff is that each instance gets its own copy of the function instead of sharing it through the prototype.

Do arrow functions use more memory than regular functions?

Marginally. Arrow functions do not create a `prototype` object, which saves a tiny amount of memory per function. But arrow functions as class fields (one copy per instance) use MORE memory than prototype methods (shared across instances). For most applications, neither difference is measurable.

If arrow functions cannot use arguments, are they less capable?

No. [Rest parameter](/tutorials/programming-languages/javascript/javascript-rest-parameters-a-complete-tutorial)s (`...args`) are a better alternative to the `arguments` object in every way: they produce a real array (not [an array](/tutorials/programming-languages/javascript/how-to-loop-through-arrays-using-js-for-loops-guide)-like object), they work with destructuring, and they can be named descriptively. The loss of `arguments` is not a limitation in practice.

Is it bad to mix arrow and regular functions in the same file?

Not at all. Most codebases use both. Arrow functions for callbacks, utility functions, and inline expressions. Regular functions or method syntax for object methods, constructors, and generators. Consistency within each use case matters more than using only one type everywhere.

How do I know if a bug is caused by wrong this in an arrow function?

If `this` is `undefined`, `Window`, or some unexpected object inside a function, check whether it is an arrow function. Add `console.log(this)` as the first line. If the value is wrong, switching to a regular function (or method shorthand) is usually the fix. The [browser debugger](/tutorials/programming-languages/javascript/basic-javascript-debugging-tips-for-beginners) can also show the `this` value in the scope panel.

Conclusion

Arrow functions are the right choice for most callbacks, array methods, and short utility functions. They are the wrong choice when you need dynamic this binding (object methods, prototype methods, DOM event handlers that reference the element), when you need the arguments object, when you need a constructor, or when you need a generator. The key principle is simple: if the function needs its own this context, use a regular function. If it needs to inherit this from the surrounding scope, use an arrow function.