Back to Blog
C#

C# LINQ SelectMany: Flattening Nested Collections

c# linq selectmany: Understand how C# LINQ SelectMany flattens nested collections into a single sequence, with syntax examples and performance tradeoffs.

LINQC#IEnumerableData TransformationFunctional Programming
A diagram showing how C# LINQ SelectMany flattens nested collections into a single sequence

What SelectMany Does

c# linq selectmany is the LINQ operator that projects each element of a source sequence into a collection, then flattens all those collections into a single sequence. If you have a List<Order> where each order contains List<OrderLine>, SelectMany produces one flat IEnumerable<OrderLine> instead of an IEnumerable<List<OrderLine>>.

The method signature is:

public static IEnumerable<TResult> SelectMany<TSource, TResult>( this IEnumerable<TSource> source, Func<TSource, IEnumerable<TResult>> selector)

The selector receives one element from the source and returns a collection. SelectMany concatenates every returned collection in source order.

How the Flattening Works

Consider a simple example:

List<string[]> words = new() { new[] { "alpha", "beta" }, new[] { "gamma" }, new[] { "delta", "epsilon", "zeta" } }; IEnumerable<string> flattened = words.SelectMany(wordList => wordList); // alpha, beta, gamma, delta, epsilon, zeta

The lambda wordList => wordList is the identity projection. Each string[] is returned as-is, and SelectMany concatenates the three arrays into one sequence. The result is lazy: nothing is materialized until you enumerate the result.

Select vs SelectMany: When the Difference Matters

Select projects each element to a single value, preserving the outer structure:

List<List<int>> groups = new() { new() { 1, 2 }, new() { 3, 4 } }; var selected = groups.Select(g => g); // IEnumerable<List<int>>, 2 elements var flattened = groups.SelectMany(g => g); // IEnumerable<int>, 4 elements
AspectSelectSelectMany
Result shapeOne output per inputZero or more outputs per input
Typical use1:1 projection1:N flattening
Nested resultPreserves nestingRemoves nesting

The distinction becomes visible when you need to work with every inner element directly. A Select result requires a nested loop; a SelectMany result can be processed with a single foreach.

Practical Example: Nested Domain Data

A common real-world scenario is a customer with multiple orders, each containing multiple line items:

public record LineItem(string Product, int Quantity); public record Order(int Id, List<LineItem> Items); public record Customer(string Name, List<Order> Orders); List<Customer> customers = GetCustomers(); IEnumerable<LineItem> allItems = customers .SelectMany(customer => customer.Orders) .SelectMany(order => order.Items);

The first SelectMany flattens customers into orders; the second flattens orders into line items. Chaining SelectMany calls is the standard way to descend multiple levels of nesting.

Query Syntax: The from ... from ... Pattern

The query syntax equivalent uses two from clauses:

IEnumerable<LineItem> allItems = from customer in customers from order in customer.Orders from item in order.Items select item;

Each additional from clause corresponds to one SelectMany call. The compiler translates this into the same method-chain form. The query syntax is often more readable when the projection involves several levels, because the range variables (customer, order, item) remain in scope for later clauses.

The Result Selector Overload

SelectMany has a second overload that accepts a result selector:

public static IEnumerable<TResult> SelectMany<TSource, TCollection, TResult>( this IEnumerable<TSource> source, Func<TSource, IEnumerable<TCollection>> collectionSelector, Func<TSource, TCollection, TResult> resultSelector)

This overload lets you combine the outer element with each inner element:

var orderLines = customers .SelectMany( customer => customer.Orders, (customer, order) => new { customer.Name, order.Id });

The result selector receives both the original source element and the current inner element. This is useful when you need context from the outer level in the final projection.

Performance and Allocation Behavior

SelectMany is lazy and does not materialize intermediate collections. Each call creates an iterator object, and chaining multiple SelectMany calls adds iterator overhead. For typical in-memory collections this overhead is negligible, but it matters in hot paths processing millions of elements.

The identity projection x => x adds a delegate invocation per element. If you are flattening a structure you already hold, consider whether the projection can be avoided. In most cases the clarity of SelectMany outweighs the small allocation cost.

For large data sets, measure before optimizing. The iterator-based pipeline is usually faster than building intermediate List<T> instances manually, because it avoids repeated array resizing and copying.

Common Mistakes and How to Avoid Them

One frequent mistake is using Select when SelectMany is needed, producing a nested sequence that requires an extra loop. Another is forgetting that the selector must return a collection. If the selector returns a single value, use Select instead.

A subtle issue arises with deferred execution. If the source collection is modified between creating the query and enumerating it, the query reflects the modified state. Materialize with ToList() or ToArray() when you need a stable snapshot.

Another edge case: an empty inner collection simply contributes no elements to the output. SelectMany handles empty sequences gracefully, which makes it safe for data where some parents have no children.

When SelectMany Is Not the Right Choice

If you need to preserve grouping boundaries, SelectMany destroys them. For example, if you need to know which order each line item belongs to, use the result selector overload or keep the nested structure with Select and process it with nested loops.

If the inner collection is expensive to materialize, SelectMany's lazy evaluation can cause repeated enumeration. Cache the inner sequence with ToList() when you need to enumerate the same flattened result multiple times.

c# linq selectmany: Practical Usage and Code Examples | RYUSLOG DEV