Skip to content
JavaScript js functions 4 min read

Arrow Functions

Arrow functions, introduced in ES2015, give you a compact syntax for writing function expressions. Beyond brevity, they differ from regular functions in one fundamental way: they do not bind their own this, arguments, super, or new.target. That single behavior makes them ideal for callbacks but disqualifies them from a few roles where a classic function is required. Knowing both the syntax and the trade-offs is what separates careful code from subtle bugs.

Arrow syntax

An arrow function is written as a parameter list, the => token, and a body. The body can be a block (with an explicit return) or a single expression that is returned implicitly.

// Block body with explicit return
const add = (a, b) => {
  return a + b;
};

// Expression body with implicit return
const square = (n) => n * n;

// Single parameter — parentheses optional
const double = n => n * 2;

// No parameters — parentheses required
const now = () => Date.now();

console.log(add(2, 3), square(4), double(5));

Output:

5 16 10

To implicitly return an object literal, wrap it in parentheses so the braces are not parsed as a function body:

const makeUser = (name, role) => ({ name, role });
console.log(makeUser("Ada", "admin"));

Output:

{ name: 'Ada', role: 'admin' }

Arrows shine in higher-order array methods, where the implicit return keeps pipelines readable:

const nums = [1, 2, 3, 4, 5, 6];

const result = nums
  .filter((n) => n % 2 === 0)
  .map((n) => n * n)
  .reduce((sum, n) => sum + n, 0);

console.log(result);

Output:

56

Lexical this

A regular function gets its own this determined by how it is called. An arrow function has no this of its own — it captures this from the surrounding (lexical) scope at the moment it is defined. This eliminates the classic “lost this” problem inside callbacks.

class Timer {
  constructor() {
    this.seconds = 0;
  }

  start() {
    // Arrow keeps `this` pointing at the Timer instance
    setInterval(() => {
      this.seconds++;
      console.log(this.seconds);
    }, 1000);
  }
}

new Timer().start();

Before arrow functions, you had to capture this manually (const self = this) or call .bind(this). The arrow makes that boilerplate disappear because this simply refers to whatever it was in start().

Arrows also have no own arguments object. References to arguments resolve to the enclosing function’s, so use rest parameters instead when you need the call’s arguments:

const collect = (...args) => args;
console.log(collect(1, 2, 3));

Output:

[ 1, 2, 3 ]

Because this is fixed at definition time, calling an arrow with .call(), .apply(), or .bind() cannot change its this. Those methods can still pass arguments, but the bound this is silently ignored.

When not to use arrows

The same lexical behavior that helps with callbacks becomes a liability in a few places.

Object methods that use this. As a method, an arrow captures this from the enclosing scope (often the module or global object), not the object itself.

const counter = {
  count: 0,
  increment: () => {
    this.count++; // `this` is NOT counter
  },
  add() {
    this.count++; // regular method — `this` is counter
  },
};

counter.add();
console.log(counter.count);

Output:

1

Constructors. Arrows cannot be called with new — they have no [[Construct]] internal method and no prototype property.

const Person = (name) => {
  this.name = name;
};
// new Person("Ada"); // TypeError: Person is not a constructor

Prototype methods and dynamic this. Event handlers or any code that relies on this being set by the caller need a regular function:

button.addEventListener("click", function () {
  this.classList.toggle("active"); // `this` is the clicked element
});

Arrow vs regular functions

FeatureArrow functionRegular function
Own thisNo — lexicalYes — depends on call site
Own argumentsNoYes
Usable as constructor (new)NoYes
Has prototype propertyNoYes
call/apply/bind change thisNoYes
HoistedNo (assigned to a variable)Declarations are hoisted
Implicit returnYes (expression body)No
Can be a generator (function*)NoYes

Best Practices

  • Reach for arrows for short callbacks — map, filter, reduce, forEach, promise .then handlers, and event listeners that do not need the element as this.
  • Use regular functions (or method shorthand) for object methods and class prototype methods that read or write this.
  • Never use an arrow as a constructor or generator; the engine cannot support new or yield there.
  • Prefer rest parameters (...args) over arguments inside arrows, since arguments is not available.
  • Add parentheses around an implicitly returned object literal: () => ({ ... }).
  • Keep implicit-return arrows to a single clear expression; switch to a block body once logic grows, for readability.
Last updated June 1, 2026
Was this helpful?