LINQ
flatten nested objects
C# programming
data transformation
object manipulation

How to flatten nested objects with linq expression

Data Structures & Algorithms practice on Codemia

Step through 300+ coding interview problems with animated visualisers that show the data structure changing as the code runs. The Blind 75 are free.

Practice algorithms

Introduction

Flattening nested objects in C# means converting a hierarchy of collections into a single flat sequence. LINQ's SelectMany is the primary tool for this — it projects each element to a collection and then flattens all the collections into one sequence. For deeper nesting, chain multiple SelectMany calls. For tree structures with arbitrary depth, use recursive methods combined with LINQ.

SelectMany — Basic Flattening

csharp
1class Department
2{
3    public string Name { get; set; }
4    public List<Employee> Employees { get; set; }
5}
6
7class Employee
8{
9    public string Name { get; set; }
10    public string Role { get; set; }
11}
12
13var departments = new List<Department>
14{
15    new Department
16    {
17        Name = "Engineering",
18        Employees = new List<Employee>
19        {
20            new Employee { Name = "Alice", Role = "Developer" },
21            new Employee { Name = "Bob", Role = "QA" }
22        }
23    },
24    new Department
25    {
26        Name = "Marketing",
27        Employees = new List<Employee>
28        {
29            new Employee { Name = "Charlie", Role = "Designer" },
30            new Employee { Name = "Diana", Role = "Manager" }
31        }
32    }
33};
34
35// Flatten: get all employees across all departments
36var allEmployees = departments.SelectMany(d => d.Employees);
37// Alice, Bob, Charlie, Diana

SelectMany with Result Selector

Include parent information alongside child elements:

csharp
1var result = departments.SelectMany(
2    dept => dept.Employees,
3    (dept, emp) => new { Department = dept.Name, Employee = emp.Name, emp.Role }
4);
5
6foreach (var item in result)
7{
8    Console.WriteLine($"{item.Employee} ({item.Role}) — {item.Department}");
9}
10// Alice (Developer) — Engineering
11// Bob (QA) — Engineering
12// Charlie (Designer) — Marketing
13// Diana (Manager) — Marketing

Query Syntax (from ... from)

csharp
1// Equivalent to SelectMany using query syntax
2var result = from dept in departments
3             from emp in dept.Employees
4             select new { dept.Name, Employee = emp.Name };
5
6// Multiple levels of nesting
7var result = from dept in departments
8             from emp in dept.Employees
9             from skill in emp.Skills  // If employees have a Skills list
10             select new { dept.Name, emp.Name, Skill = skill };

Flattening Nested Collections

csharp
1// Flatten a list of lists
2var nestedLists = new List<List<int>>
3{
4    new List<int> { 1, 2, 3 },
5    new List<int> { 4, 5 },
6    new List<int> { 6, 7, 8, 9 }
7};
8
9var flat = nestedLists.SelectMany(list => list);
10// 1, 2, 3, 4, 5, 6, 7, 8, 9
11
12// Flatten arrays of arrays
13int[][] jagged = { new[] { 1, 2 }, new[] { 3, 4, 5 }, new[] { 6 } };
14var flat = jagged.SelectMany(arr => arr).ToArray();
15// [1, 2, 3, 4, 5, 6]

Three Levels Deep

csharp
1class Company
2{
3    public string Name { get; set; }
4    public List<Department> Departments { get; set; }
5}
6
7var companies = new List<Company> { /* ... */ };
8
9// Flatten: Company → Department → Employee
10var allEmployees = companies
11    .SelectMany(c => c.Departments)
12    .SelectMany(d => d.Employees);
13
14// With parent context
15var allEmployees = companies
16    .SelectMany(c => c.Departments, (c, d) => new { Company = c.Name, Department = d })
17    .SelectMany(cd => cd.Department.Employees, (cd, e) => new
18    {
19        cd.Company,
20        Department = cd.Department.Name,
21        Employee = e.Name
22    });

Recursive Flattening (Tree Structures)

For tree structures with arbitrary depth:

csharp
1class TreeNode
2{
3    public string Name { get; set; }
4    public List<TreeNode> Children { get; set; } = new();
5}
6
7// Recursive flatten
8static IEnumerable<TreeNode> Flatten(TreeNode node)
9{
10    yield return node;
11    foreach (var child in node.Children)
12    {
13        foreach (var descendant in Flatten(child))
14        {
15            yield return descendant;
16        }
17    }
18}
19
20// Using LINQ with recursion
21static IEnumerable<TreeNode> FlattenLinq(TreeNode node)
22{
23    return new[] { node }
24        .Concat(node.Children.SelectMany(FlattenLinq));
25}
26
27// Usage
28var root = new TreeNode
29{
30    Name = "Root",
31    Children = new List<TreeNode>
32    {
33        new TreeNode { Name = "A", Children = new List<TreeNode>
34        {
35            new TreeNode { Name = "A1" },
36            new TreeNode { Name = "A2" }
37        }},
38        new TreeNode { Name = "B" }
39    }
40};
41
42var allNodes = Flatten(root).Select(n => n.Name);
43// Root, A, A1, A2, B

Flattening with Index

csharp
1var departments = new List<Department> { /* ... */ };
2
3var indexed = departments
4    .SelectMany((dept, deptIndex) =>
5        dept.Employees.Select((emp, empIndex) => new
6        {
7            DeptIndex = deptIndex,
8            EmpIndex = empIndex,
9            Department = dept.Name,
10            Employee = emp.Name
11        }));

Flattening Dictionaries

csharp
1var grouped = new Dictionary<string, List<string>>
2{
3    ["Fruits"] = new List<string> { "Apple", "Banana" },
4    ["Veggies"] = new List<string> { "Carrot", "Pea" }
5};
6
7var flat = grouped.SelectMany(
8    kvp => kvp.Value,
9    (kvp, item) => new { Category = kvp.Key, Item = item }
10);
11// { Fruits, Apple }, { Fruits, Banana }, { Veggies, Carrot }, { Veggies, Pea }

Common Pitfalls

  • Null child collections: If dept.Employees is null, SelectMany throws NullReferenceException. Use dept.Employees ?? Enumerable.Empty<Employee>() or initialize collections in constructors.
  • Confusing Select and SelectMany: Select(d => d.Employees) returns IEnumerable<List<Employee>> (nested). SelectMany(d => d.Employees) returns IEnumerable<Employee> (flat). Use SelectMany for flattening.
  • Deferred execution: SelectMany is lazily evaluated. If the source collection changes before you enumerate the result, you get the updated data. Call .ToList() to materialize if you need a snapshot.
  • Stack overflow with deep recursion: Recursive Flatten() on very deep trees can overflow the stack. For trees deeper than ~1000 levels, use an iterative approach with an explicit Stack<T>.
  • Losing parent context: Plain SelectMany(d => d.Employees) loses the department information. Use the overload with a result selector SelectMany(d => d.Employees, (d, e) => new { d.Name, e }) to keep parent data.

Summary

  • Use SelectMany to flatten one level of nested collections into a single sequence
  • Chain multiple SelectMany calls for deeper nesting (Company → Department → Employee)
  • Use the result selector overload to preserve parent context in flattened results
  • For tree structures with arbitrary depth, use recursive methods with Concat and SelectMany
  • Always handle null child collections to prevent NullReferenceException in SelectMany

Course
Intermediate
27 lessons
14 hours
OOD Fundamentals

Master object-oriented design from first principles, SOLID, design patterns, and classic interview problems with hands-on coding.

View the course
Data Structures & Algorithms practice on Codemia

Step through 300+ coding interview problems with animated visualisers that show the data structure changing as the code runs. The Blind 75 are free.

Practice algorithms