JavaScript Arrow Functions Table

PatternEquivalent toField note
x => x * 2One param, expression bodyImplicit return - the expression IS the result
(a, b) => a + bMulti-paramParentheses required with 0 or 2+ params
() => 42No paramsEmpty parens; common in callbacks and delays
x => ({ a: x })Return object literalParens around braces required - bare {} reads as a body
const f = x => { doWork(); return x; }Body with statementsBraces = explicit return needed
arr.map(x => x.id)Callback shorthandThe everyday home of arrow functions
async x => fetch(x)Async arrowawait inside works; returns a promise like any async fn
Reference: the MDN arrow functions reference. The one structural difference from regular functions: arrows have NO own this, arguments, or prototype - this is lexical, inherited from the enclosing scope. That is the feature (callbacks keep the surrounding this without .bind) and the limitation (they cannot be constructors or methods that need their own this). Bottom line: arrows for callbacks and short transforms, regular functions for methods and anything needing its own this. The expression-vs-body split: expression bodies return implicitly; brace bodies need an explicit return. Related tools: closures table (the scopes arrows capture), this table for the lexical binding, and array methods table for the callbacks they power.

Arrow functions are the compact form: x => x * 2 replaces function(x) { return x * 2; } - same behavior, one line, implicit return. The table below is the working seven: the parameter and body forms, the object-literal parentheses that trip everyone once, and the async arrow.

Bottom line: the one structural difference from regular functions is that arrows have NO own this, arguments, or prototype - this is lexical, captured from the enclosing scope at creation. That is the feature (callbacks keep the surrounding this without .bind) and the limitation (arrows cannot be constructors, methods that need their own this, or generators).

The honest part: the expression-vs-body split is where beginners lose the return value. Expression bodies (after =>) return implicitly; brace bodies need an explicit return - writing x => { x * 2 } returns undefined because the braces make a body and there is no return statement. The parenthesized object literal x => ({a: x}) exists for exactly this disambiguation.

How to use

  1. Start with the shortest form that works: one param and one expression need no parens or braces at all.
  2. Add parens as parameters grow: (a, b), (a = 1), or () for none - and braces when the body has statements.
  3. Switch to a regular function when the callback needs its own this (methods, constructors) or when named for stack traces.

Frequently asked questions

Why does my arrow function return undefined?

Because braces changed the body type. x => x * 2 has an expression body - the expression IS the return value. x => { x * 2 } has a BLOCK body - the braces contain statements, and without a return statement the function returns undefined. The fix is removing the braces for one-expression bodies, or adding return inside them. The object-literal variant needs the extra parentheses: x => ({ value: x }) - without them, the parser reads the braces as a block, not an object.

When should I use an arrow function instead of a regular function?

Arrows win when you want this from the enclosing scope - the canonical case is a callback inside a method that needs the method's this: addEventListener('click', () => this.open()) keeps this as the component. Regular functions win when you need their own this (class methods, constructors), named functions for stack traces, or generator syntax. The modern default is arrows for callbacks and regular functions (or class methods) for everything with a name.

Can arrow functions be constructors?

No - arrows have no [[Construct]] internal method, so new (x => {}) throws a TypeError. They also lack prototype and arguments. The design is intentional: arrows are lightweight expressions meant for callbacks, not blueprints for objects. If something needs new, it wants a regular function or a class - which also gets you a name for stack traces and the semantics other developers expect.

How do async arrow functions work?

async x => fetch(x) puts await inside an arrow like any async function - the arrow returns a promise that resolves with the expression's value. The pattern powers the one-liner async callback: arr.map(async item => await process(item)) - with the honest caveat that map does not await those promises; Promise.all(arr.map(...)) collects them. Top-level await in modules and async IIFE wrappers complete the picture.

Related tools