1. Introduction
Let's remember how regular Select works:
var names = users.Select(user => user.Name);
Each user gets mapped to their name — you get a list of names. Easy! But what if you want a list of all orders from all users?
var ordersList = users.Select(user => user.Orders);
What happens here? For each user, you get a list of orders. So the result is a collection of collections (IEnumerable<List<Order>>). It's like a shelf with boxes: to get to each order, you have to open each box (the user's order list) one by one.
We want to "unpack" the whole array so we can work with it like a regular list of orders. That's where SelectMany comes to the rescue.
Analogy: Unpacking Boxes
Imagine all your stuff isn't just mixed together, but stored in boxes. Each box is a collection, like one user's purchases. If you want to see ALL the stuff at once (say, for a big audit), you wouldn't go through each box and then each item inside, over and over. It's way easier to dump everything from all the boxes into one big pile — and sort through that. That's how SelectMany works.
2. What is SelectMany?
SelectMany is a LINQ method that takes a collection of collections and turns it into one "flat" collection. By "flat," we mean the resulting sequence doesn't have inner lists anymore — all the elements from the nested collections are on the same level, like they've been "pulled out." No nesting dolls, just one long chain of values.
The method signature looks like this:
IEnumerable<TResult> SelectMany<TSource, TResult>(
this IEnumerable<TSource> source,
Func<TSource, IEnumerable<TResult>> selector
)
It works simply: you have a collection (say, a list of users), and each element contains another collection inside (like a list of that user's orders). SelectMany takes each user, grabs their orders, and merges them into one single stream of orders — no "wrapping," no nesting. That's what makes it so handy when you need to go from a "list of lists" to a regular list.
3. Simple Examples
Example 1: List of Users and Their Orders
Let's start with our app model, which we've been building up:
// Let's say these classes are already from previous lessons
class User
{
public string Name { get; set; }
public List<Order> Orders { get; set; }
}
class Order
{
public int Id { get; set; }
public string ProductName { get; set; }
}
Suppose we have a list of users:
var users = new List<User>
{
new User
{
Name = "Ivan",
Orders = new List<Order>
{
new Order { Id = 1, ProductName = "Book" },
new Order { Id = 2, ProductName = "Pen" }
}
},
new User
{
Name = "Maria",
Orders = new List<Order>
{
new Order { Id = 3, ProductName = "Laptop" }
}
}
};
Task: get a list of all products ever bought by all users.
Regular Select:
var ordersPerUser = users.Select(user => user.Orders);
// IEnumerable<List<Order>>
Here, you end up with a bunch of "boxes," each with a user's order list inside.
SelectMany:
var allOrders = users.SelectMany(user => user.Orders);
// IEnumerable<Order>
Now you get one "long" collection of all orders!
Visualization
| User | Orders |
|---|---|
| Ivan | Book, Pen |
| Maria | Laptop |
- After Select: [[Book, Pen], [Laptop]] — list of lists
- After SelectMany: [Book, Pen, Laptop] — single list
Example 2: Working with Multiple Levels of Nesting
If, for example, an order has a list of products (a cart), then you can use SelectMany multiple times — just like with nesting dolls!
class Product
{
public string Name { get; set; }
}
class Order
{
public int Id { get; set; }
public List<Product> Products { get; set; }
}
class User
{
public string Name { get; set; }
public List<Order> Orders { get; set; }
}
var users = new List<User>
{
new User
{
Name = "Petr",
Orders = new List<Order>
{
new Order
{
Id = 10,
Products = new List<Product>
{
new Product { Name = "Phone" },
new Product { Name = "Charger" }
}
}
}
}
};
To get all products from all orders of all users, use double SelectMany:
var allProducts = users
.SelectMany(u => u.Orders)
.SelectMany(o => o.Products);
// IEnumerable<Product>
This way, you unpack one level of "boxes," then the next.
Example 3: Using SelectMany in Query Syntax
If you prefer the SQL-like LINQ syntax (Query Syntax), then SelectMany is expressed with several from statements in a row:
var allProducts =
from user in users
from order in user.Orders
from product in order.Products
select product;
Here, each next from takes an element from the collection returned by the previous one.
users
└─ user1
└─ order1
└─ product1, product2
└─ order2
└─ product3
└─ user2
└─ order3
└─ product4
Query Syntax goes through all the paths and returns one long list of products.
4. Usage Options and Nuances
SelectMany for Transformation and Filtering
With SelectMany, you can not only unpack, but also filter or project elements at the same time. For example, pick only products more expensive than 1000 euros from all orders:
var expensiveProducts = users
.SelectMany(u => u.Orders)
.SelectMany(o => o.Products)
.Where(p => p.Price > 1000);
Or — with a bit of logic right inside SelectMany (using an extra selector):
var expensiveProducts = users
.SelectMany(u => u.Orders.SelectMany(o => o.Products))
.Where(p => p.Price > 1000);
It's a matter of taste: both ways break the problem into separate sequential steps.
Using the SelectMany Overload with Projection
There's an extra "advanced" overload that lets you not only unpack, but also immediately shape the result. For example: get pairs of "User Name — Product Name":
var userProductPairs = users.SelectMany(
user => user.Orders.SelectMany(order => order.Products),
(user, product) => new { user.Name, product.Name }
);
How does this work? The first parameter — user => user.Orders.SelectMany(order => order.Products) — unpacks all the user's products, the second combines the original user object and the product inside the nested enumerator.
5. Important Points and Common Mistakes
You might run into a situation where you forgot to add SelectMany and instead of a flat sequence, you got an array of "boxes." For example, when you used regular Select instead of SelectMany where you needed to unpack all the elements at once. As a result, you can't loop through or process the elements directly.
It's super easy to get confused if collections are nested, especially with deep nesting. Make it a rule: if you need a flat list, not a list of lists, — just use SelectMany.
Where is this useful in practice?
Any time you have an object-collection, and each element has its own collection (like "user — orders," "course — students," "news — comments"), you'll come back to SelectMany. In interviews, this is a "favorite" topic: not all beginners can tell Select from SelectMany and properly unpack collections.
In real projects, this lets you write clean, concise, and fast code without extra nesting. On the .NET platform, it's the standard and optimized way to handle "layered" collections.
Difference between Select and SelectMany
The Select method keeps the structure: if you use it on a collection of users, each with a list of orders, the result is a collection of collections — like [[A, B], [C]]. That's handy if you want to keep working with the data by groups (like by user).
But SelectMany "flattens" — it merges all the nested collections into one big sequence: [A, B, C]. That's useful when you don't care about grouping, and you want to work with all the inner elements as one big set — like finding all products, no matter which user or order they belong to.
+---------------------+ +--------------------+
| users | | allOrders |
+---------------------+ +--------------------+
| Ivan: [A, B] | --+ +-> Order A |
| Maria: [C] | --+ | +-> Order B |
+---------------------+ | | +-> Order C |
| | +--------------------+
Select: | |
[[A, B], [C]] <--------+ +
|
SelectMany: <--------+
[A, B, C]
Roughly speaking:
- Select → "give me the order lists by user";
- SelectMany → "just give me all the orders".
GO TO FULL VERSION