map()
ES5+Creates a new array with the results of calling a provided function on every element in the calling array.
Syntax
array.map(callback(element, index, array), thisArg)Parameters
callback Function Function that produces an element of the new Array
thisArg any optionalValue to use as this when executing callback
Return Value
A new array with each element being the result of the callback function
Examples
const numbers = [1, 2, 3, 4];
const doubled = numbers.map(x => x * 2);
console.log(doubled); 📌 When to Use
Use map() when you need to transform each element of an array into a new value while keeping a strict one-to-one relationship between input and output. Because the result always has exactly the same length as the source, map() is the right choice whenever every element produces exactly one transformed element: extracting a property from a list of objects, converting units, formatting dates for display, or reshaping API records into view models. If the transformation might drop elements, reach for filter() first, or use flatMap() with conditional empty arrays, because returning undefined from a map() callback leaves undefined entries in the result rather than removing them. If you only need to run a side effect - logging, writing to the DOM, pushing into another structure - use forEach() instead; calling map() and discarding the returned array allocates memory for nothing and misleads readers into hunting for a result that is never used. When you need to collapse the array into a single value, such as a sum or a lookup object, reduce() expresses that intent more directly. A good rule of thumb: choose map() when you can describe the operation as "the same list, with each item converted".
⚠️ Common Mistakes
Using map() purely for side effects and ignoring the returned array. map() allocates a brand-new array on every call, so if you never use the result you are paying for memory and garbage collection you do not need - and readers will search for a return value that never gets consumed. Use forEach() or a for...of loop instead.
Forgetting that map() always returns an array of exactly the same length as the original. It cannot skip or remove elements; a callback that returns nothing simply fills that slot with undefined. Developers who expect filtering behavior end up with arrays full of undefined entries that later crash property accesses downstream.
Accidentally returning undefined by forgetting the return statement when the arrow callback uses curly braces. (x) => { x * 2 } evaluates the expression and throws the value away, so every element becomes undefined - either drop the braces for an implicit return or add an explicit return statement.
Passing a function that accepts extra parameters directly to map(). The classic trap is ["1", "7", "11"].map(parseInt), which returns [1, NaN, 3]: map() supplies three arguments (element, index, array), and parseInt interprets the index as a radix. Wrap the call explicitly instead: arr.map(s => parseInt(s, 10)).
Assuming the callback runs for every index of a sparse array. Per the ECMAScript specification, map() skips holes entirely (the callback never fires for them) yet preserves them in the output, so [1, , 3].map(x => x * 2) yields [2, , 6] - a subtle bug source when arrays come from Array(n) without fill().
Mutating the objects inside the array while mapping. map() creates a new array but not new elements: the callback receives references to the original objects, so writing obj.price = 0 inside the callback silently corrupts the source data. Return fresh objects with spread syntax instead of editing the ones you were given.
✅ Best Practices
Keep the callback pure: compute the output only from its arguments and avoid writing to outer variables or mutating the source array. Pure callbacks make the transformation trivially testable, safe to refactor, and immune to surprises when the same array is mapped again somewhere else.
When you need both selection and transformation, run filter() first and then map(), so the transform executes only on elements you intend to keep. This ordering avoids wasted work on discarded items and prevents half-transformed placeholder values from leaking through a later, weaker filter condition.
Use concise arrow functions for single-expression transforms such as arr.map(x => x * 2), but extract a named function like formatUser once the logic spans several lines. Names document intent at the call site, and named functions appear in stack traces when a transform throws.
Return new objects rather than editing the ones you receive: users.map(u => ({ ...u, active: true })) keeps the original list intact. This matters in React, Vue, and Svelte, where change detection and memoization rely on reference changes rather than deep comparison.
Reach for flatMap() when one input element can produce zero or many outputs. Returning [] to drop an element and [a, b] to expand one lets a single pass replace a map() plus filter() chain while keeping the code declarative.
⚡ Performance Notes
map() allocates a new array up front and runs in O(n) time and memory. In V8 the engine knows the output length in advance, so mapping over a packed array of small integers or doubles is extremely fast - the callback invocation, not the iteration itself, is usually the dominant cost. Chains such as data.map(a).filter(b).map(c) create a full intermediate array at every step; for hot paths over hundreds of thousands of elements, fuse the steps into a single map(), a reduce(), or a plain for loop to cut allocations and garbage-collector pressure. Also avoid sparse inputs: holes force V8 out of its fast packed-elements representation into holey or dictionary mode, slowing every subsequent operation on that array. Below roughly ten thousand elements, the difference between map() and a hand-written loop is rarely measurable in real applications, so prefer whichever reads better.
🌍 Real World Example
Converting API Response to Display Format
A very common frontend pattern: an API returns raw records whose shape does not match what a UI component expects. Here map() converts each user object into the { value, label } pair that a select or dropdown component consumes, combining two name fields into one display string. The original users array remains untouched, so other parts of the application can keep working with the raw data. Transforming at the boundary like this keeps server shapes and UI shapes decoupled, which makes later API changes far less painful to absorb.
// API returns: [{id: 1, firstName: 'John', lastName: 'Doe'}, ...]
const users = await fetchUsers();
const dropdownOptions = users.map(user => ({
value: user.id,
label: `${user.firstName} ${user.lastName}`
}));
// Result: [{value: 1, label: 'John Doe'}, ...]