LINQ
flatten nested objects
C# programming
data transformation
object manipulation

How to flatten nested objects with linq expression

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

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

Related reading
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
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

All Rights Reserved.