JavaScript Arrow Functions Table
| Pattern | Equivalent to | Field note |
|---|---|---|
x => x * 2 | One param, expression body | Implicit return - the expression IS the result |
(a, b) => a + b | Multi-param | Parentheses required with 0 or 2+ params |
() => 42 | No params | Empty parens; common in callbacks and delays |
x => ({ a: x }) | Return object literal | Parens around braces required - bare {} reads as a body |
const f = x => { doWork(); return x; } | Body with statements | Braces = explicit return needed |
arr.map(x => x.id) | Callback shorthand | The everyday home of arrow functions |
async x => fetch(x) | Async arrow | await inside works; returns a promise like any async fn |
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
- Start with the shortest form that works: one param and one expression need no parens or braces at all.
- Add parens as parameters grow: (a, b), (a = 1), or () for none - and braces when the body has statements.
- 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.