Back to Blog
C#

C# List FindIndex: Locating Elements by Predicate

c# list findindex: Learn how to use List<T>.FindIndex in C# to locate elements by a predicate, handle missing matches, and choose between FindIndex, IndexOf, and LINQ.

C#List<T>FindIndexPredicateSearch
Illustration of a C# list with an index pointer highlighting a matching element using FindIndex.

c# list findindex requires a clear understanding of the core syntax, runtime behavior, and practical implementation patterns demonstrated in the examples below.

The List<T>.FindIndex method returns the zero-based index of the first element that matches a specified predicate. If no element matches, it returns -1. The simplest overload takes a Predicate<T>:

List<string> names = new List<string> { "Alice", "Bob", "Charlie" }; int index = names.FindIndex(name => name.StartsWith("B")); Console.WriteLine(index); // 1

The predicate is a delegate that receives each element and returns a boolean. The method iterates the list in order and stops at the first true result. This behavior is identical to List<T>.Find, except Find returns the element itself rather than its index.

Using the Start Index and Count Overloads

When you need to search only a portion of the list, use the overload with a start index and count:

List<int> numbers = new List<int> { 10, 20, 30, 40, 50 }; int index = numbers.FindIndex(2, 2, n => n > 25); // searches indices 2 and 3, returns 2 (value 30)

The start index is inclusive, and the count specifies how many elements to examine. This overload is useful when you want to skip a known prefix or search a specific window without creating a sublist.

Handling No Match and Negative Return Values

A common mistake is to use the returned index directly without checking for -1. If the predicate never returns true, FindIndex returns -1, which is a valid index from the end if used with array indexing. Always check the result before using it:

int index = names.FindIndex(n => n == "Zoe"); if (index >= 0) { // use index } else { // handle not found }

The same rule applies to all overloads. The -1 value is consistent across IndexOf and FindIndex, so you can rely on it for control flow.

Performance Considerations for Repeated Searches

FindIndex performs a linear scan in the worst case, so its time complexity is O(n). For a single search, this is usually acceptable. However, if you need to locate elements repeatedly in the same list, consider building a Dictionary<TKey, int> that maps keys to indices once, then use O(1) lookups. This trades memory for speed and is especially beneficial for large lists with frequent lookups.

var indexMap = names .Select((name, idx) => new { name, idx }) .ToDictionary(x => x.name, x => x.idx);

Be aware that the dictionary approach requires unique keys. If duplicates exist, ToDictionary will throw. Use GroupBy or a Lookup if duplicates are possible.

Alternatives: IndexOf, Find, and LINQ

List<T>.IndexOf searches for an exact element using equality, not a predicate. It is faster when you already have the object reference and want to find its position. Find returns the matching element, not the index. LINQ's Select plus IndexOf can achieve the same result, but FindIndex is more direct and avoids intermediate allocations.

MethodReturnsPredicateUse case
FindIndexIndexYesFind position by condition
IndexOfIndexNoFind position by reference/value
FindElementYesGet the element itself
LINQ Select+IndexOfIndexYesWhen you already use LINQ

Choose FindIndex when you need the index for further operations, such as removing the element or inserting at that position. Use IndexOf for exact matches, and Find when you only need the element.

Common Pitfalls and Edge Cases

The predicate is evaluated once per element until a match is found. If the predicate has side effects, they may not run for all elements. Also, modifying the list during iteration inside the predicate is unsafe and can cause undefined behavior. The method assumes the list is not modified during the search.

Another edge case is the start index being equal to the list count. In that case, the method returns -1 immediately, because there are no elements to examine. If the start index is negative or the count is invalid, an ArgumentOutOfRangeException is thrown. Always validate the range when using the overload with a start index and count.

When to Use FindIndex vs a Custom Loop

For simple searches, FindIndex is more readable than a manual for loop. However, if you need to break early based on additional logic or track multiple matches, a loop gives you more control. For example, finding all indices that match a predicate requires a loop or LINQ:

List<int> allIndices = new List<int>(); for (int i = 0; i < numbers.Count; i++) { if (numbers[i] > 20) allIndices.Add(i); }

FindIndex only returns the first match. If you need all matches, you must implement the loop yourself or use Select with Where to filter indices.

Using FindIndex with Custom Types and Complex Predicates

FindIndex works with any List<T>, including custom classes. The predicate can access properties and perform complex logic. For instance, finding a user by ID:

public class User { public int Id { get; set; } public string Name { get; set; } } List<User> users = GetUsers(); int index = users.FindIndex(u => u.Id == 42); if (index >= 0) { users[index].Name = "Updated"; }

This pattern is common in update operations where you need the index to modify the original list. Keep the predicate simple and free of side effects to maintain clarity and avoid unexpected behavior.

c# list findindex: Practical Usage and Code Examples | RYUSLOG DEV