Demystifying .map vs .flatMap in Nested Promises

Sunil Chaulagain
3 min readJan 10, 2025

When working with arrays in JavaScript, the .map method is a familiar tool to iterate over elements, transforming them into something new. But when dealing with nested arrays of promises, .flatMap can simplify your code, making it cleaner and more efficient. In this article, we’ll explore the differences between .map and .flatMap, and how to handle nested promises elegantly.

What’s the Problem?

Imagine you’re building a system where you need to perform asynchronous operations on a nested array structure. For example:

  • Outer loop: Iterate over a list of users.
  • Inner loop: Perform operations on each user’s associated tasks.

Here’s a simplified example:

const users = [
{ id: 1, name: 'Alice', tasks: [101, 102] },
{ id: 2, name: 'Bob', tasks: [201, 202] },
];

const processTask = (userId, taskId) =>
new Promise((resolve) =>
setTimeout(() => resolve(`Processed Task ${taskId} for User ${userId}`), 100)
);

For every user, we want to process each of their tasks concurrently.

The .map Approach

The .map method is great for creating a new array based on the results of a callback. However, when dealing with nested arrays, it produces nested structures.

Here’s how it looks in this scenario:

const tasks = users.map((user) =>
user.tasks.map((task) => processTask(user.id, task))
);

console.log(tasks);
/*
[
[Promise, Promise], // Tasks for Alice
[Promise, Promise] // Tasks for Bob
]
*/
// To resolve these promises, we need to flatten the array:
await Promise.all(tasks.flat());

This works, but it introduces unnecessary nesting ([[Promise, Promise], [Promise, Promise]]), which makes the code harder to read.

The .flatMap Solution

With .flatMap, you can immediately create a flat array of promises, removing the need for manual flattening.

const tasks = users.flatMap((user) =>
user.tasks.map((task) => processTask(user.id, task))
);
console.log(tasks);
/*
[Promise, Promise, Promise, Promise]
*/
// Resolve all tasks
await Promise.all(tasks);

This approach is cleaner and avoids the intermediate nesting of arrays.

A Real-World Example

Let’s imagine you’re working on a project where:

  • You have a list of contacts.
  • Each contact is associated with multiple campaigns.
  • For every campaign, you need to perform an API call.

Here’s the generic setup:

const contacts = [
{ id: 1, name: 'Alice', campaigns: [101, 102] },
{ id: 2, name: 'Bob', campaigns: [201, 202] },
];
const performApiCall = (contactId, campaignId) =>
new Promise((resolve) =>
setTimeout(() => resolve(`Processed Campaign ${campaignId} for Contact ${contactId}`), 100)
);

Using .map with Nested Promise.all

await Promise.all(
contacts.map((contact) =>
Promise.all(
contact.campaigns.map((campaign) => performApiCall(contact.id, campaign))
)
)
);

While this works, the nested Promise.all calls make it harder to read. Flattening this logic with .flatMap makes it cleaner.

Optimized Version with .flatMap

await Promise.all(
contacts.flatMap((contact) =>
contact.campaigns.map((campaign) => performApiCall(contact.id, campaign))
)
);

This version removes unnecessary nesting and focuses on creating a flat array of promises.

When to Use .map vs .flatMap

Use .map:

  • When you need to preserve a nested structure.
  • For example, if you want to group tasks by users in the final result:
const results = await Promise.all(
contacts.map((contact) =>
Promise.all(
contact.campaigns.map((campaign) => performApiCall(contact.id, campaign))
)
)
);
console.log(results);
// [
// [Result, Result], // Tasks for Alice
// [Result, Result] // Tasks for Bob
// ]

Use .flatMap:

  • When you want a flat array of promises to process everything at once.
  • It’s more concise and avoids unnecessary nesting.

Performance Considerations

Memory Usage:

  • .map with .flat() creates an intermediate nested array, consuming more memory.
  • .flatMap combines the operations into one step, saving resources.

Readability:

  • .flatMap leads to cleaner code in scenarios where nesting isn’t required.

Conclusion

When dealing with arrays of promises, choosing between .map and .flatMap depends on whether you need to preserve nesting. If your goal is a flat array of operations (common in many async workflows), .flatMap simplifies your code, making it both efficient and easier to read.

Next time you encounter nested arrays of promises, remember the power of .flatMap to streamline your solution!

--

--

Sunil Chaulagain
Sunil Chaulagain

Written by Sunil Chaulagain

Senior Software engineer with in-depth knowledge and 10 years experience of software development. Proficient in LAMP and MERN stack on AWS infrastructure.

No responses yet