JavaScript Arrow Functions

Learn arrow function syntax, implicit returns, and lexical this with clear practical examples.

TL;DR

  1. 01Write concise callbacks using => instead of the function keyword.
  2. 02Skip return and braces for one-line expressions with implicit returns.
  3. 03Inherit this from the enclosing scope instead of redefining it.

Tips

  1. 01Use arrow functions for short callbacks inside map() and filter(), since they keep the code easy to scan.
  2. 02Name longer arrow functions by assigning them to a const, since stack traces then show the variable name.

Warnings

  1. 01Do not use arrow functions as methods on objects when those methods need access to the object through this.
  2. 02Arrow functions cannot be used as constructors, so calling one with new throws a TypeError instead of creating an instance.

Basic Syntax

    One parameter

    Drop the parentheses when the function takes exactly one parameter.

    const double = n => n * 2;
    double(4); // 8
    Multiple parameters

    Wrap parameters in parentheses when the function takes two or more.

    const multiply = (a, b) => a * b;
    multiply(2, 5); // 10
    No parameters

    Use empty parentheses when the function takes no parameters at all.

    const greet = () => 'Hello!';
    greet(); // "Hello!"
    Anonymous by default

    Arrow functions are anonymous and are usually assigned to a variable.

    const handlers = [];
    handlers.push(() => console.log('clicked'));
    Use const

    Assign arrow functions with `const` to prevent accidental reassignment.

    const sayHi = () => 'Hi';
    sayHi = () => 'Yo'; // TypeError: read-only

Returning Values

    Implicit return

    Drop the braces and the return keyword to return a single expression.

    const sum = (a, b) => a + b;
    Block body

    Use curly braces with return for multi-line function bodies and logic.

    const check = num => {
      if (num > 10) return 'big';
      return 'small';
    };
    Object literal

    Wrap returned object literals in parentheses to avoid a syntax error.

    const make = id => ({ id, active: true });
    Array methods

    Implicit returns work well inside array methods like `filter()` and `map()`.

    const evens = [1, 2, 3, 4]
      .filter(n => n % 2 === 0)
      .map(n => n * 10); // [20, 40]
    Pick a style

    Match the return style to the code's complexity and readability needs.

    const isEven = n => n % 2 === 0;  // implicit
    const classify = n => {           // block
      if (n < 0) return 'negative';
      return n % 2 === 0 ? 'even' : 'odd';
    };

Promise and Async Patterns

    Promise chains

    Most real-world arrow functions live inside `.then()`, `.catch()`, or async wrappers.

    fetch('/api/user')
      .then(res => res.json())
      .then(data => console.log(data))
      .catch(err => console.error(err));
    Promise.all()

    Run independent async calls together and destructure results once every promise resolves.

    const loadDashboard = async () => {
      const [users, posts] = await Promise.all([
        fetch('/api/users').then(r => r.json()),
        fetch('/api/posts').then(r => r.json()),
      ]);
    };
    Async arrow

    Use an arrow function as the body of an async function for compact definitions.

    const getUser = async (id) => {
      const res = await fetch(`/users/${id}`);
      return res.json();
    };
    Async IIFE

    Run an async arrow immediately when you need `await` outside any named function.

    (async () => {
      const data = await fetchDashboardStats();
      console.log(data);
    })();
    try/catch

    Wrap `await` calls in `try/catch` to handle rejected promises inside async arrows.

    const loadUser = async (id) => {
      try {
        const res = await fetch(`/users/${id}`);
        return await res.json();
      } catch (err) {
        console.error('Failed to load user', err);
      }
    };

Lexical This

    Arrow inherits this

    An arrow function inherits `this` from its surrounding scope, so it stays bound.

    function Timer() {
      this.count = 0;
      setInterval(() => this.count++, 1000); // works
    }
    Regular function this

    A regular function creates its own `this`, which can cause unexpected values.

    function Timer() {
      this.count = 0;
      setInterval(function() { this.count++; }, 1000); // breaks
    }
    Class methods

    This behavior makes callbacks inside class methods work without manual binding.

    class Counter {
      count = 0;
      increment = () => { this.count++; };
    }
    Method callbacks

    Arrow callbacks inside a method inherit `this` from that method, not the caller.

    const cart = {
      items: [{ price: 10 }, { price: 25 }],
      total() {
        return this.items.reduce(
          (sum, i) => sum + i.price, 0
        );
      },
    };
    cart.total(); // 35
    Event listeners

    Bind event handlers with arrow functions so `this` still points at the class instance.

    class Button {
      clicks = 0;
      constructor(el) {
        el.addEventListener('click', () => {
          this.clicks++;
        });
      }
    }

Limitations

    No arguments object

    Arrow functions have no `arguments` object; use rest parameters to collect arguments instead.

    function legacyLogger() {
      return arguments.length;
    }
    const modernLogger = (...args) => args.length;
    No constructors

    Arrow functions cannot be used as constructors with the `new` keyword.

    const Person = (name) => { this.name = name; };
    new Person('Alex'); // TypeError: not a constructor
    No generators

    Arrow functions cannot be used as generator functions with `yield`.

    function* range(n) {
      for (let i = 0; i < n; i++) yield i;
    }
    [...range(3)]; // [0, 1, 2]
    No prototype

    Arrow functions have no `prototype` property, since they can never act as constructors.

    const Greeter = () => {};
    Greeter.prototype; // undefined

In Practice

FAQ