C#
Programming
IEnumerable
Indexing
LINQ

How to get the index of an element in an IEnumerable?

System Design practice on Codemia

Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.

Practice system design

Introduction

IEnumerable<T> does not have a built-in IndexOf method because it represents a forward-only sequence with no concept of position. To find the index of an element, use LINQ's Select to project each element with its index, then filter with FirstOrDefault. Alternatively, write a simple extension method, or convert to a List<T> and use its IndexOf method. Each approach trades off readability, performance, and reusability.

Method 1: LINQ Select with Index

LINQ's Select overload provides the index of each element:

csharp
1using System.Linq;
2
3IEnumerable<string> names = new[] { "Alice", "Bob", "Charlie", "Bob" };
4
5int index = names
6    .Select((value, i) => new { value, i })
7    .FirstOrDefault(x => x.value == "Charlie")
8    ?.i ?? -1;
9
10Console.WriteLine(index);  // 2

This iterates until the first match is found, then stops. If the element is not found, it returns -1.

Using Tuples (C# 7+)

csharp
1int index = names
2    .Select((value, i) => (value, i))
3    .FirstOrDefault(x => x.value == "Charlie")
4    .i;  // 2
5
6// Warning: if not found, default tuple has i = 0, not -1
7// Use a nullable approach for safety:
8int? index = names
9    .Select((value, i) => (value, index: (int?)i))
10    .FirstOrDefault(x => x.value == "NotFound")
11    .index ?? -1;

Method 2: Extension Method

Create a reusable IndexOf extension method for IEnumerable<T>:

csharp
1public static class EnumerableExtensions
2{
3    public static int IndexOf<T>(this IEnumerable<T> source, T value)
4    {
5        int index = 0;
6        var comparer = EqualityComparer<T>.Default;
7
8        foreach (var item in source)
9        {
10            if (comparer.Equals(item, value))
11                return index;
12            index++;
13        }
14
15        return -1;  // Not found
16    }
17
18    // Overload with predicate
19    public static int IndexOf<T>(this IEnumerable<T> source, Func<T, bool> predicate)
20    {
21        int index = 0;
22
23        foreach (var item in source)
24        {
25            if (predicate(item))
26                return index;
27            index++;
28        }
29
30        return -1;
31    }
32}
33
34// Usage
35IEnumerable<string> names = new[] { "Alice", "Bob", "Charlie" };
36
37int i1 = names.IndexOf("Bob");           // 1
38int i2 = names.IndexOf(n => n.Length > 5); // 2 (Charlie)
39int i3 = names.IndexOf("Dave");           // -1

Method 3: Convert to List

The simplest approach if you need multiple lookups:

csharp
1IEnumerable<string> names = GetNames();  // Some IEnumerable source
2
3List<string> list = names.ToList();
4int index = list.IndexOf("Charlie");  // Uses List<T>.IndexOf
5Console.WriteLine(index);  // 2 (or -1 if not found)

ToList() materializes the entire sequence into memory. This is wasteful if you only need one index lookup, but efficient if you need multiple lookups because List<T>.IndexOf is O(n) per call without re-enumerating the source.

Method 4: Manual Loop

csharp
1IEnumerable<int> numbers = Enumerable.Range(0, 1000);
2
3int target = 42;
4int index = -1;
5int current = 0;
6
7foreach (int number in numbers)
8{
9    if (number == target)
10    {
11        index = current;
12        break;
13    }
14    current++;
15}
16
17Console.WriteLine(index);  // 42

This is the most efficient approach for a single lookup — no allocations beyond the enumerator.

Finding All Indices

csharp
1IEnumerable<string> names = new[] { "Alice", "Bob", "Alice", "Charlie", "Alice" };
2
3// All indices where element matches
4var indices = names
5    .Select((value, i) => new { value, i })
6    .Where(x => x.value == "Alice")
7    .Select(x => x.i)
8    .ToList();
9
10Console.WriteLine(string.Join(", ", indices));  // 0, 2, 4

With Custom Equality

csharp
1IEnumerable<string> names = new[] { "Alice", "bob", "CHARLIE" };
2
3// Case-insensitive search
4int index = names
5    .Select((value, i) => (value, i))
6    .FirstOrDefault(x => string.Equals(x.value, "Bob", StringComparison.OrdinalIgnoreCase))
7    .i;  // 1
8
9// Extension method with custom comparer
10public static int IndexOf<T>(this IEnumerable<T> source, T value, IEqualityComparer<T> comparer)
11{
12    int index = 0;
13    foreach (var item in source)
14    {
15        if (comparer.Equals(item, value))
16            return index;
17        index++;
18    }
19    return -1;
20}
21
22// Usage
23int i = names.IndexOf("bob", StringComparer.OrdinalIgnoreCase);  // 1

.NET 9+ Index Operator

Starting in .NET 9, LINQ added Index() which returns (int Index, TSource Item) pairs:

csharp
1// .NET 9+
2IEnumerable<string> names = new[] { "Alice", "Bob", "Charlie" };
3
4foreach (var (index, name) in names.Index())
5{
6    Console.WriteLine($"{index}: {name}");
7}
8// 0: Alice
9// 1: Bob
10// 2: Charlie

Common Pitfalls

  • IEnumerable may be non-repeatable: Some IEnumerable<T> sources (database queries, network streams, generators) can only be enumerated once. Calling Select(...).FirstOrDefault(...) and then enumerating again produces different results or throws. Materialize with ToList() first if you need multiple passes.
  • Default tuple has index 0, not -1: Using FirstOrDefault with value tuples returns (default, 0) when no match is found, which looks like index 0. Use nullable types or the anonymous object approach to distinguish "found at 0" from "not found".
  • O(n) performance: Every approach iterates the sequence element by element. If you need frequent index lookups, convert to a List<T> or build a Dictionary<T, int> for O(1) lookups.
  • Multiple enumerations: Chaining .Select().Where().Select() still only enumerates once (LINQ is lazy). But calling IndexOf twice on the same IEnumerable<T> enumerates it twice. Consider materializing if you need multiple lookups.
  • Index instability: The index of an element in an IEnumerable<T> depends on the enumeration order, which may change if the source is modified or if the source does not guarantee order (e.g., HashSet<T>). Only rely on indices for ordered, stable sources.

Summary

  • Use Select((value, i) => ...) with FirstOrDefault for a one-off LINQ-based index lookup
  • Write an IndexOf extension method for reusable, clean syntax
  • Convert to List<T> and use List.IndexOf() if you need multiple lookups
  • All approaches are O(n) — IEnumerable<T> has no concept of position
  • Be careful with FirstOrDefault on value tuples — default index is 0, not -1

Related reading
Course
Beginner
27 lessons
10 hours
System Design Fundamentals

Build a strong foundation in designing scalable, reliable distributed systems.

View the course
Track what you have practised

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

System Design practice on Codemia

Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.

Practice system design

All Rights Reserved.