Back to Blog
C#

C# Search Array: IndexOf, Find, and LINQ

c# search array: Learn how to search arrays in C# using IndexOf, Find, FindIndex, and LINQ, and understand when each approach fits best.

C#ArrayLINQSearchPerformance
Illustration of a magnifying glass over a C# array with search methods.

When you need to locate an element in a C# array, the right search method depends on the type of match, the expected result, and how often the search runs. The .NET base class library provides several ways to search arrays, from the static Array methods to LINQ extensions, and each has distinct behavior around missing values, type handling, and allocation. This article walks through the primary options for a c# search array scenario and explains the tradeoffs so you can pick the one that fits your data and performance requirements.

Searching with Array.IndexOf

The most direct way to find an exact value in an array is Array.IndexOf. It returns the zero-based index of the first occurrence, or -1 if the value is not present. For value types, it compares by value; for reference types, it uses the default equality comparer, which calls Equals on the objects.

string[] names = { "Ada", "Grace", "Linus", "Margaret" }; int index = Array.IndexOf(names, "Linus"); Console.WriteLine(index); // 2 int missing = Array.IndexOf(names, "Alan"); Console.WriteLine(missing); // -1

Array.IndexOf is a static method that works on any array, including multidimensional arrays, though for multidimensional arrays it returns the index in the flattened representation. For most single-dimensional arrays, it is the simplest and fastest option when you need an exact match and you know the value at compile time.

One limitation is that it does not accept a predicate. If you need to search based on a property or a condition, you have to use one of the predicate-based methods.

Using Array.Find and Array.FindIndex

When the search condition is more complex than equality, Array.Find and Array.FindIndex accept a Predicate<T> delegate. Array.Find returns the first element that matches, or default(T) if no match is found. Array.FindIndex returns the index of the first match, or -1.

record Person(string Name, int Age); Person[] people = { new("Ada", 36), new("Grace", 45), new("Linus", 54) }; Person found = Array.Find(people, p => p.Age > 40); Console.WriteLine(found?.Name); // Grace int foundIndex = Array.FindIndex(people, p => p.Age > 40); Console.WriteLine(foundIndex); // 1

Array.Find returns null for reference types when no match exists, but for value types it returns default(T), which is often 0 or false. That can be ambiguous if a legitimate element has the default value. In such cases, prefer Array.FindIndex and check for -1, or use a nullable value type as the return type if you need the element itself.

Both methods scan the array from the beginning and stop at the first match, so they have the same time complexity as a linear search. They are part of the Array class and do not require LINQ, which can be useful in codebases that avoid LINQ for performance or style reasons.

LINQ Search Methods for Arrays

Arrays implement IEnumerable<T>, so all LINQ extension methods are available. The most common ones for searching are First, FirstOrDefault, Where, Any, and Contains. These methods are convenient when you are already working with LINQ or when you need to chain additional operations.

using System.Linq; int[] numbers = { 10, 20, 30, 40 }; int firstOver25 = numbers.First(n => n > 25); Console.WriteLine(firstOver25); // 30 bool hasZero = numbers.Contains(0); Console.WriteLine(hasZero); // False int? maybe = numbers.FirstOrDefault(n => n > 100); Console.WriteLine(maybe); // 0 (default int)

First throws InvalidOperationException when no element matches, while FirstOrDefault returns default(T). For reference types, FirstOrDefault returns null, which is easy to check. For value types, the default value can be a valid data value, so you need to decide whether that ambiguity matters.

Contains is useful when you only need to know whether an element exists, not its position. It uses the default equality comparer and returns a boolean. Any with a predicate is similar but accepts a condition instead of a specific value.

LINQ methods introduce a small amount of overhead because they allocate an enumerator and, for methods like Where, they create iterator state machines. For arrays of a few thousand elements, this overhead is usually negligible. For hot paths or very large arrays, the static Array methods are slightly more efficient because they avoid the enumerator allocation and use direct indexing internally.

Handling Missing Values and Defaults

A common mistake when searching arrays is to treat default(T) as a valid result. For value types, Array.Find and FirstOrDefault both return 0 when no match is found. This can silently produce incorrect logic if 0 is a legitimate value in your data.

Consider a struct that represents a temperature reading. A reading of 0 degrees is valid, but Array.Find would return default(Reading) if no match exists, which is also 0 degrees. To avoid this, use Array.FindIndex and check the index, or use a nullable type:

Reading? found = Array.Find(readings, r => r.Timestamp == targetTime); if (found.HasValue) { // process found.Value }

For reference types, null is a clear sentinel, but you still need to guard against null elements in the array itself. If your array can contain null entries, a predicate like x => x != null && x.Id == id is safer than relying on the default equality comparer.

Another option is to use Array.Exists or Array.TrueForAll when you only need a boolean result. Array.Exists returns true if any element matches the predicate, and Array.TrueForAll returns true only if every element matches. These are part of the Array class and avoid the allocation of LINQ iterators.

Performance and Allocation Considerations

All the search methods described so far perform a linear scan in the worst case. The time complexity is O(n), where n is the array length. If you search the same array repeatedly, the linear scan cost adds up. Sorting the array and using Array.BinarySearch reduces the search to O(log n), but it requires the array to be sorted and the elements to be comparable.

int[] sortedNumbers = { 10, 20, 30, 40, 50 }; int index = Array.BinarySearch(sortedNumbers, 30); Console.WriteLine(index); // 2

Array.BinarySearch returns a negative value if the element is not found; the bitwise complement of that value gives the insertion point. This is useful when you need to maintain a sorted array and want to find where a new element would go.

The static Array methods avoid the allocation of an enumerator, but they still may allocate a delegate when you pass a lambda expression. In modern .NET, lambdas are often cached by the compiler, so the delegate allocation happens once per call site, not per invocation. LINQ methods allocate an enumerator for each call, but for small arrays this is rarely a measurable cost.

If you are searching a very large array in a performance-critical loop, consider whether you can replace the array with a HashSet<T> or Dictionary<TKey, TValue> for O(1) lookups. That changes the data structure, but it is the right decision when membership checks or key-based lookups dominate the workload.

Choosing the Right Search Approach

The choice between Array.IndexOf, Array.Find, and LINQ methods comes down to three questions: what you need as a result, whether your condition is an equality check or a predicate, and whether the code runs in a hot path.

Use Array.IndexOf when you need the index of an exact value and the type is simple. It is the most direct and does not require a predicate.

Use Array.FindIndex when you need the index based on a condition, especially for value types where default(T) is ambiguous. It gives you a clear -1 sentinel.

Use Array.Find when you need the element itself and you are working with reference types, or when you can safely handle the default value for value types.

Use LINQ First or FirstOrDefault when you are already in a LINQ pipeline, or when you prefer the fluent style and the performance difference is not relevant. Use Any or Contains for boolean checks.

Use Array.BinarySearch when the array is sorted and you perform many searches. The initial sort cost is amortized over repeated lookups, and the logarithmic search is significantly faster for large arrays.

Finally, remember that arrays are fixed-size. If the collection changes frequently, a List<T> might be a better fit, and it exposes the same search methods plus BinarySearch on the list. The search logic remains the same, but the data structure is more flexible for additions and removals.