JAVASCRIPT / ARRAYS
map for one-to-one transformations
Use Array.prototype.map to build a new array with one transformed value per element, keeping length and index alignment while leaving the source untouched.
What you will learn
- Reach for map when every input needs exactly one output and the length must not change.
- Return a value on every path; a brace-bodied arrow without return yields undefined.
- Know the callback receives (value, index, array), which is what breaks map(parseInt).
- Spread into a new object instead of mutating the objects map hands your callback.
Understanding map for one-to-one transformations
map asks one question about every element: what value should stand in this position instead? The array it returns has the same length as the source, and index i of the output is produced from index i of the input, which is what makes this a one-to-one transformation. Where filter decides which elements survive, map never adds or drops a slot; it only replaces contents. That guarantee is worth leaning on: if the input had 40 rows, the mapped result has 40 rows regardless of what the callback does.
Mechanically, map walks the array, calls your function with (element, index, wholeArray), and stores the returned value in a fresh array at the same index. Because it stores whatever comes back, a callback that returns nothing still fills its slot, with undefined, so a skipped element is not a removed element. The source array is never written to; map allocates and returns a new array, which is why the result has to be assigned or chained to be worth anything.
The useful mental model is a value factory, not a loop that does things. If the callback's return value is irrelevant because you are only logging, pushing somewhere else, or firing a request, you are allocating an array of undefined that nobody reads, and for...of or forEach states the intent better. Kept to pure value production, maps chain predictably because every stage's length is known in advance: filter first when you want to transform fewer items, map first when the condition you are testing depends on the transformed value.
const cities = [
{ name: 'Oslo', tempC: 12 },
{ name: 'Lima', tempC: 19 },
{ name: 'Cairo', tempC: 34 }
];
// one output value per input object: same order, same length
const fahrenheit = cities.map(city => city.tempC * 9 / 5 + 32);
const rows = cities.map((city, i) => `${i}: ${city.name}`);
console.log(JSON.stringify(fahrenheit));
console.log(JSON.stringify(rows));
console.log(fahrenheit.length === cities.length);
console.log(cities[0].tempC);map is a length-preserving transformation: whatever the callback returns for an element lands at that same index in a brand-new array.
Worked examples
A block body needs an explicit return
Shows why wrapping the arrow body in braces produces an array of undefined instead of transformed values.
const words = ['carbon', 'iron', 'neon'];
const forgotten = words.map(w => { w.toUpperCase(); });
const fixed = words.map(w => { return w.toUpperCase(); });
const concise = words.map(w => w.toUpperCase());
console.log(forgotten.length, forgotten[0]);
console.log(fixed.join(','));
console.log(concise.join(','));Example explained
Line 1`w => { w.toUpperCase(); }` computes the uppercase string and throws it away, so the function returns undefined.
Line 2forgotten.length is still 3: map fills a slot for every element no matter what the callback returns.
Line 3Adding `return` inside the braces, or dropping the braces entirely, both put the string into the slot.
Line 4toUpperCase does not modify the original string, so words itself is untouched in all three cases.
The index argument leaking into parseInt
Demonstrates that map passes three arguments to the callback, which silently corrupts functions that accept a second parameter.
const digits = ['10', '10', '10', '10'];
console.log(digits.map(parseInt).join(','));
console.log(digits.map(Number).join(','));
console.log(digits.map(s => parseInt(s, 10)).join(','));Example explained
Line 1map calls the callback as callback(value, index, array), so parseInt receives the index as its radix.
Line 2Index 0 means "guess the radix" and gives 10, but radix 1 is invalid so the second call returns NaN.
Line 3Index 2 parses '10' as binary (2) and index 3 as base 3 (3): wrong answers with no error raised.
Line 4Number ignores extra arguments, and an explicit arrow lets you pin the radix yourself.
New array, same objects
Shows that the array map returns is new but the elements inside it are still references to the original objects unless you copy them.
const cart = [
{ sku: 'A1', qty: 2 },
{ sku: 'B7', qty: 1 }
];
const doubled = cart.map(item => ({ ...item, qty: item.qty * 2 }));
const mutated = cart.map(item => { item.qty = 99; return item; });
console.log(JSON.stringify(doubled));
console.log(JSON.stringify(cart));
console.log(mutated[0] === cart[0]);Example explained
Line 1The parentheses in `item => ({ ... })` are required, otherwise the braces would be read as a function body.
Line 2Spreading item builds a fresh object per element, so doubled keeps its values even after cart is changed later.
Line 3The second map assigns to item.qty, and since item is the very object stored in cart, the source array changes.
Line 4mutated[0] === cart[0] is true: map copied the reference, not the object it points to.
Important notes
map's optional second argument sets `this` inside the callback, but arrow functions take `this` lexically and ignore it, so pass a regular function if you need that binding.
On a sparse array, map does not call the callback for missing indexes yet copies those gaps into the result, so the callback can run fewer times than the array's length.
Common mistakes
Writing arr.map(x => { x * 2 }) with braces and no return, which yields [undefined, undefined, ...]; the failure surfaces much later as an empty join or NaN sum.
Using map where forEach or for...of belongs, just to log or push items, which allocates a full array of undefined for every element and hides the fact that the code exists only for its side effects.
Returning nothing for elements you wanted to drop, expecting them to disappear; map cannot shorten the array, so undefined values travel downstream and poison later arithmetic or string building.
Try it yourself
Change, predict, then run
In a browser console, create const people = [{first: 'Ada', last: 'Lovelace'}, {first: 'Grace', last: 'Hopper'}] and use map to produce ['Lovelace, Ada', 'Hopper, Grace']. Then confirm the result's length equals people.length and that people[0] still has exactly its two original properties.
Open the JavaScript workspaceCheck your understanding
An array of five numbers is run through nums.map(n => { if (n > 10) return n; }). What comes back?
- An array containing only the numbers greater than 10
- A TypeError, because the callback does not return on every path
- An array of five items in which every number of 10 or less became undefined
- An array of five items in which every number of 10 or less kept its original value
Show answer
map stores whatever the callback returns at the same index, and a function that reaches its end without hitting a return statement returns undefined, so all five slots exist and the failing ones hold undefined. The first option describes filter; map has no mechanism for removing a slot, and omitting a return is perfectly legal JavaScript, so nothing throws.