Arrow functions and regular functions look similar and behave differently in four specific ways: how this is determined, whether they have their own arguments, whether they can be used with new, and whether they are hoisted. Arrow functions are shorter to write and much more common; regular functions are still the right tool in a handful of places.
What is the syntactic difference?
An arrow function replaces the function keyword with an arrow and drops the name. When there is a single parameter you can leave off the parentheses.
const double = function (n) {
return n * 2;
};
const triple = (n) => {
return n * 3;
};
const quadruple = (n) => n * 4;
console.log(double(2)); // 4
console.log(triple(2)); // 6
console.log(quadruple(2)); // 8
All three are functions and all three behave the same way when called. The last one uses the implicit return: a single expression body is returned without the return keyword.
That shorthand is where most arrow function mistakes come from. If you write a block body but forget the return, the function gives back undefined:
const toRupees = (paise) => paise / 100;
const label = (name) => `Hello, ${name}`;
console.log(toRupees(24950)); // 249.5
console.log(label("Ada")); // "Hello, Ada"
// A block body needs an explicit return.
const broken = (n) => {
n * 2; // computed, then thrown away
};
console.log(broken(2)); // undefined
How do they differ with this?
This is the difference that actually matters. A regular function's this depends on how it is called. An arrow function has no this of its own — it uses the this of the scope where it was written, and that cannot be changed by calling it in a different way.
const timer = {
seconds: 0,
start() {
const arrowTick = () => {
this.seconds += 1;
};
const regularTick = function () {
this.seconds += 1;
};
arrowTick();
regularTick.call(this); // a regular function needs `this` handed to it
return this.seconds;
},
};
console.log(timer.start()); // 2
Inside the method, this is the timer object. The arrow function picks that up automatically because it was written inside the method. The regular function would lose it the moment it was called on its own, so it has to be given the right this with .call(this) or .bind(this).
That is the whole rule: use an arrow function when you want to keep the surrounding this, and a regular function when you want the call site to decide. The full story of this — including what happens when a method is detached from its object — has its own lesson later in this tutorial, in the classes and prototypes module.
What about the arguments object?
A regular function has its own arguments, an array-like list of everything it was called with. An arrow function does not — it uses the arguments of whatever scope encloses it.
function outer() {
const arrowCount = () => arguments.length;
function regularCount() {
return arguments.length;
}
return { arrow: arrowCount(), regular: regularCount(1) };
}
console.log(outer(1, 2, 3)); // { arrow: 3, regular: 1 }
The arrow function sees three arguments — those of outer — while the regular function sees only the one it was given. Outside a function entirely, an arrow has no arguments at all: in an ES module, referencing it throws a ReferenceError.
The arguments object is also array-like rather than a real array. It has a length and numeric indexes, but none of the array methods:
function hasMap() {
return typeof arguments.map;
}
function countArgs(...args) {
return args.length;
}
console.log(hasMap(1, 2)); // "undefined"
console.log(countArgs(1, 2, 3)); // 3
That is exactly why the rest parameter — covered next — is the better tool. It produces a real array, and it is the modern replacement for arguments in new code.
Can an arrow function be used as a constructor?
No. Arrow functions have no prototype property and cannot be called with new.
const Arrow = () => {};
try {
new Arrow();
} catch (error) {
console.log("caught:", error.constructor.name, "-", error.message);
}
function Regular() {}
const instance = new Regular();
console.log("regular works:", instance instanceof Regular); // true
The attempt throws a TypeError saying Arrow is not a constructor, while the regular function constructs an object normally. If you need a constructible function, you need a regular one — which is why classes, when you reach them, are built on regular function syntax under the hood.
Are arrow functions hoisted?
No. Only function declarations are. An arrow function stored in a const behaves exactly like any other const — it cannot be used before its declaration runs.
console.log(typeof declared); // "function" — hoisted
function declared() {}
try {
console.log(typeof assigned);
} catch (error) {
console.log("caught:", error.constructor.name, "-", error.message);
}
const assigned = () => {};
The declaration reports "function" because the engine registered it before running anything. The arrow function throws a ReferenceError with the message Cannot access 'assigned' before initialization — the binding exists but has not been given a value yet.
Why are arrows so common in callbacks?
Because a callback is usually called by somebody else, with a receiver you do not control. A regular function handed to setTimeout, addEventListener or an array method gets a this that depends entirely on the caller. An arrow ignores all of that and keeps the this of the place it was written.
const form = {
submitted: false,
handleSubmit() {
setTimeout(() => {
this.submitted = true;
console.log("submitted:", this.submitted);
}, 0);
},
};
form.handleSubmit();
The output is submitted: true. The arrow function was written inside handleSubmit, so its this is the form object no matter who ends up calling it. Had the callback been a regular function, this would have belonged to whatever called it, and the assignment would have gone somewhere else entirely — or thrown, in the strict mode that ES modules run in.
This is the practical reason arrows dominate callback position in modern JavaScript, and the reason they are the wrong choice for object methods.
So which should I use?
| Situation | Use |
|---|---|
| Callbacks, event handlers, array methods | Arrow function |
Short helper that should keep the surrounding this | Arrow function |
A method on an object that needs its own this | Regular function, or a shorthand method |
Something that must work with new | Regular function |
| Something that must be callable before its definition | Function declaration |
A function that needs its own arguments | Regular function with rest parameters |
As a working rule: reach for an arrow function unless you have a specific reason not to. It is shorter, it cannot be misused as a constructor, it has no arguments of its own to confuse you, and it inherits this from where it was written — which is what you want almost every time.
The differences in summary
- Arrows have no name of their own and are not hoisted, so they must be defined before they are called.
- An arrow with a single-expression body returns it implicitly. A block body needs an explicit
return. - An arrow inherits
thisfrom the scope it was written in. A regular function takesthisfrom the call site. - An arrow has no own
arguments. Use rest parameters instead. - An arrow cannot be used with
new, and it has noprototype. - For callbacks and array methods, arrows are the default choice.
Try it yourself: take an object with a counter and write two methods on it — one as a shorthand method and one as an arrow function property. Call both as counter.increment() and watch the arrow version fail to update the counter, because its this is not the object. Then rewrite the arrow version as a shorthand method and confirm it works. That single experiment explains the rule better than any amount of reading.