C#
List\`\`\`\`\`<T>\`\`\`\`\`
search
programming
.NET

Finding an item in a List using C

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

Searching inside List<T> is one of the most frequent operations in C# business code. The best method depends on whether you need a boolean check, an item, an index, or many matches. This guide compares practical options and shows how to choose for correctness and performance.

Choosing the Right Search API

List<T> offers several built-in methods, each aimed at a specific use case:

  • Contains for simple equality checks.
  • Find to return first match by predicate.
  • FindIndex for position lookup.
  • FindAll for multiple matches.

Using the right method makes code easier to read and avoids unnecessary allocations.

csharp
1using System;
2using System.Collections.Generic;
3
4var products = new List<string> { "book", "pen", "bag" };
5
6bool hasPen = products.Contains("pen");
7string? firstWithB = products.Find(p => p.StartsWith("b"));
8int indexOfBag = products.FindIndex(p => p == "bag");
9
10Console.WriteLine($"hasPen={hasPen}, firstWithB={firstWithB}, indexOfBag={indexOfBag}");

Searching Objects with Predicates

For object lists, define clear predicates and consider null safety.

csharp
1using System;
2using System.Collections.Generic;
3
4record User(int Id, string Email, bool Active);
5
6var users = new List<User>
7{
8    new(1, "[email protected]", true),
9    new(2, "[email protected]", false),
10    new(3, "[email protected]", true)
11};
12
13User? activeUser = users.Find(u => u.Active && u.Email == "[email protected]");
14int inactiveIndex = users.FindIndex(u => !u.Active);
15
16Console.WriteLine(activeUser);
17Console.WriteLine(inactiveIndex);

Keep predicates small and domain-oriented. If predicate logic grows, extract it into a named method for readability and testability.

LINQ Options and Tradeoffs

LINQ methods like FirstOrDefault, SingleOrDefault, and Where are expressive and often preferred in service code.

csharp
1using System.Linq;
2
3var firstActive = users.FirstOrDefault(u => u.Active);
4var allActive = users.Where(u => u.Active).ToList();

Use SingleOrDefault only when data integrity requires exactly one match. It throws if more than one item matches, which can be useful but should be intentional.

LINQ readability is strong, but in very hot loops direct List<T> methods may allocate less and run faster.

Performance Considerations at Scale

List<T> search is linear time, so repeated lookups on large collections can become expensive. If you perform frequent key-based lookups, move to dictionary-based indexing.

csharp
1using System.Collections.Generic;
2
3var userById = new Dictionary<int, User>();
4foreach (var u in users)
5{
6    userById[u.Id] = u;
7}
8
9if (userById.TryGetValue(3, out var user))
10{
11    Console.WriteLine(user.Email);
12}

A dictionary gives near constant-time key lookup and is usually better for repeated access by unique identifier.

Defensive Search Patterns

In APIs and background jobs, missing items are normal outcomes. Avoid throwing exceptions for expected misses.

csharp
1User? maybeUser = users.Find(u => u.Id == 99);
2if (maybeUser is null)
3{
4    Console.WriteLine("User not found");
5}

This keeps control flow predictable and avoids noisy exception logs.

Search Strategy in Request Pipelines

In web APIs, repeated list scans inside each request handler can become a hidden latency cost. If the collection changes rarely, build a lookup index once and refresh it only when source data changes. This pattern reduces per-request work and makes performance more predictable under load.

When data mutates frequently, measure before optimizing. For small lists, linear search may still be simpler and fast enough. Profile with realistic traffic rather than microbenchmarks alone.

Common Pitfalls

A common mistake is using Find and then dereferencing without null checks. If no item matches, the result is null for reference types.

Another issue is confusing Find with Where. Find returns one item, while Where returns an enumerable sequence.

Developers also overuse LINQ inside nested loops, creating avoidable allocations and repeated scans. Pre-indexing often removes the bottleneck.

Finally, equality semantics matter. Contains relies on equality implementation. For custom types, ensure Equals and GetHashCode behavior matches business expectations.

Summary

  • Pick search methods based on required output: bool, item, index, or collection.
  • Use predicates for object searches and keep them readable.
  • Prefer dictionary indexing for frequent key-based lookups.
  • Handle not-found cases as normal control flow.
  • Validate equality behavior when using Contains with custom types.

Course illustration
Course illustration

All Rights Reserved.