When to Avoid Using Arrow Functions in JavaScript
Arrow functions are concise and useful, but they are wrong for object methods, constructors, event handlers that need this, and a few other cases. Learn when to reach for a regular function instead.
Arrow functions are concise, elegant, and everywhere in modern JavaScript. But they are not a drop-in replacement for regular functions. They have deliberate limitations, no own this, no arguments object, no prototype, that make them the wrong choice in specific situations.
Using an arrow function where a regular function belongs leads to undefined values, silent failures, or runtime errors. This article covers the cases where you should reach for a regular function instead of an arrow.
1. Object Methods
Arrow functions should not be used as object methods. They do not have their own this, so this inside the method refers to the enclosing scope, not the object:
const user = {
name: "Alex",
greet: () => {
console.log(`Hi, I'm ${this.name}`);
},
};
user.greet(); // "Hi, I'm undefined"The fix is the method shorthand syntax, which correctly binds this to the object because regular methods receive their own this based on how they are called, not where they were defined:
const user = {
name: "Alex",
greet() {
console.log(`Hi, I'm ${this.name}`);
},
};
user.greet(); // "Hi, I'm Alex"This applies to methods in objects, classes, and prototypes. If you need this to refer to the calling object, use a regular function or method shorthand.
2. Constructors
Arrow functions cannot be used as constructors. They lack a prototype property and throw a TypeError when called with new:
const Person = (name) => {
this.name = name;
};
const p = new Person("Alex"); // TypeError: Person is not a constructorRegular functions and classes are the correct tools for creating objects with new, since they have the internal machinery the new keyword depends on:
function Person(name) {
this.name = name;
}
const p = new Person("Alex");
console.log(p.name); // "Alex"3. Event Handlers That Need this
In DOM event handlers, this normally refers to the element that fired the event. Arrow functions break that behavior:
button.addEventListener("click", function() {
console.log(this); // The button element
this.classList.toggle("active"); // Works correctly
});
button.addEventListener("click", () => {
console.log(this); // The enclosing scope (window or module)
this.classList.toggle("active"); // TypeError or wrong element
});If your handler needs this to be the event target, use a regular function. If you do not need this, arrows are fine, but be explicit about why you chose one over the other.
4. When You Need the arguments Object
Arrow functions do not have the arguments object. If you reference arguments inside an arrow function, you get the enclosing function's arguments instead:
function outer() {
const inner = () => {
console.log(arguments); // outer's arguments, possibly unexpected
};
inner("inner-arg");
}
outer("outer-arg"); // ["outer-arg"], not ["inner-arg"]Use rest parameters when you need variable arguments in an arrow function. A rest parameter collects every argument into a real array with none of the ambiguity the arguments object introduces:
const logAll = (...args) => {
console.log(args);
};
logAll(1, 2, 3); // [1, 2, 3]But if you specifically need the arguments object, for example in a function that must interface with legacy code, use a regular function.
5. Recursive Named Functions
Arrow functions are always anonymous. You can assign them to a variable and call that variable recursively, but you cannot reference the function by its own name:
// This works (variable self-reference)
let factorial = (n) => {
if (n <= 1) return 1;
return n * factorial(n - 1);
};
// But if the variable is reassigned, it breaks
const oldFactorial = factorial;
factorial = null;
oldFactorial(5); // TypeError: factorial is not a functionFor recursion, a named function expression or declaration is safer, because the internal name always points to the original function regardless of what happens to the outer variable:
const factorial = function fact(n) {
if (n <= 1) return 1;
return n * fact(n - 1); // Uses internal name, safe from reassignment
};6. Generator Functions
Arrow functions cannot be generators. There is no arrow syntax equivalent to function*, so the yield keyword has no valid meaning inside an arrow function body:
// SyntaxError
const generator = () => {
yield 1;
};Use a regular generator function instead, adding an asterisk after the function keyword to mark it as a generator that can yield values:
function* generator() {
yield 1;
yield 2;
}7. Functions Added to a Prototype
When adding methods to a prototype, this needs to refer to the instance. Arrow functions will not work:
function Counter() {
this.count = 0;
}
// Wrong: arrow function, this is not the instance
Counter.prototype.increment = () => {
this.count++;
};
// Correct: regular function, this is the instance
Counter.prototype.increment = function() {
this.count++;
};8. When call, apply, or bind Is Needed
Arrow functions ignore call, apply, and bind. Their this is fixed at definition time and cannot be overridden at call time:
const add = (a, b) => this.value + a + b;
const obj = { value: 100 };
console.log(add.call(obj, 1, 2)); // NaN -- this is not objIf you need to dynamically set this, use a regular function.
Decision Guide
With eight separate cases to remember, it helps to collapse them into a single yes-or-no path. The flow below walks through the questions that matter most, in the order that eliminates arrow functions fastest:
The diagram above summarizes the decision flow. If you need this, the arguments object, new, yield, or prototype methods, use a regular function. For everything else, arrow functions are the cleaner choice.
For the basics of arrow function syntax, see JavaScript Arrow Functions: A Complete ES6 Guide. For the broader comparison with regular functions, see JavaScript Function Expressions vs Declarations.
Rune AI
Key Insights
- Do not use arrow functions for object methods -- this will not point to the object.
- Arrow functions cannot be constructors and throw TypeError when called with new.
- Do not use arrow functions as event handlers that need this to be the event target.
- Arrow functions lack arguments, prototype, and cannot be generators.
- Use regular function declarations for methods, constructors, and recursive named functions.
Frequently Asked Questions
Can I mix arrow and regular functions in the same codebase?
Are arrow functions faster than regular functions?
Should I use arrow functions in React class components?
Conclusion
Arrow functions are a great tool, but they are not a universal replacement for regular functions. Avoid them for object methods, constructors, dynamic this patterns, and when you need the arguments object or the prototype chain. Knowing when not to use them is just as important as knowing the syntax.
More in this topic
JavaScript While Loop Explained: A Complete Guide
A while loop repeats code as long as a condition stays true. Learn the syntax, see practical examples, and understand when a while loop is the right choice over a for loop.
JavaScript Loops Tutorial: for, while, do while
Loops let JavaScript repeat code without writing it twice. Learn the three core loop types, what each one does, and when to choose one over another.
How to Loop Through Arrays Using JS for Loops Guide
Looping through arrays is the most practical use of JavaScript for loops. Learn how to access each element by index, avoid off-by-one errors, and choose between for, for...of, and forEach.