Reader Stacks

Arrow Functions in JavaScript (ES6)

Shorter syntax is the visible part — the change that actually matters is how arrow functions handle this, which is what makes them the right (and sometimes wrong) choice in specific situations.

Arrow functions, introduced in ES6 (2015), are often described purely as shorter syntax for writing a function — that's true, but the more important difference is how they handle the this keyword, which changes what they're actually well-suited for compared to a traditional function.

1. Basic syntax

// Traditional function
function add(a, b) {
  return a + b;
}

// Arrow function
const add = (a, b) => {
  return a + b;
};

// Arrow function, implicit return (no braces, no explicit return keyword)
const add = (a, b) => a + b;

When an arrow function's body is a single expression, omitting the curly braces makes that expression's value the function's implicit return — this only works for a single expression, not a multi-statement function body, which still needs braces and an explicit return.

2. Single parameter — parentheses are optional

const double = (x) => x * 2;
const double = x => x * 2; // parentheses can be dropped for exactly one parameter

3. The real difference: arrow functions don't have their own "this"

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

  start() {
    setInterval(function() {
      this.seconds++; // BROKEN — 'this' here refers to something else entirely,
                       // not the Timer instance, because a traditional function
                       // gets its own 'this' binding based on how it's called
    }, 1000);
  }
}
class Timer {
  constructor() {
    this.seconds = 0;
  }

  start() {
    setInterval(() => {
      this.seconds++; // WORKS — an arrow function has no 'this' of its own,
                       // so it uses 'this' from the surrounding scope (the start() method)
    }, 1000);
  }
}

This is genuinely the most important practical difference, not a minor detail — a traditional function creates its own this binding determined by how it's called (which, inside a callback like setInterval, is often not what's intuitively expected). An arrow function has no this of its own at all; it transparently uses whatever this was in the enclosing scope where the arrow function was defined, which is exactly the behavior wanted for a callback inside a method.

4. Where this matters most: callbacks inside class methods and array methods

class ShoppingCart {
  constructor() {
    this.items = [];
  }

  addItems(newItems) {
    newItems.forEach(item => {
      this.items.push(item); // 'this' correctly refers to the ShoppingCart instance
    });
  }
}

Array methods like forEach, map, and filter that accept a callback are a very common place this distinction shows up in practice — using an arrow function for the callback means this inside it still refers to whatever this was outside the callback (here, the class instance), without needing an extra workaround.

5. When NOT to use an arrow function: object methods

// BROKEN
const counter = {
  count: 0,
  increment: () => {
    this.count++; // 'this' does NOT refer to 'counter' here —
                   // arrow functions get 'this' from their enclosing scope,
                   // which for an object literal method is not the object itself
  },
};
// CORRECT — use a traditional method shorthand instead
const counter = {
  count: 0,
  increment() {
    this.count++; // 'this' correctly refers to 'counter'
  },
};

This is the flip side of the same rule, and a genuine common mistake: an arrow function used directly as an object's method doesn't get this bound to that object, because arrow functions never create their own this binding at all — they inherit it from whatever scope surrounds them, which for a plain object literal isn't the object itself.

6. Arrow functions can't be used as constructors

const Product = (name) => {
  this.name = name;
};

new Product('Widget'); // TypeError: Product is not a constructor

Because arrow functions have no this of their own (and no prototype property), they can't be used with new — a traditional function declaration or a class is still required anywhere a constructor is needed.

Topics: Developer Productivity