LINQ
Lambda expressions
C#
.NET
query optimization

LINQ contains and a Lambda query

Master System Design with Codemia

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

Introduction

Contains is one of the most common LINQ tools for membership tests, especially inside Where lambdas. The important detail is that the performance and semantics depend on what is being queried: a local in-memory collection, or a remote provider such as Entity Framework that translates the lambda into SQL.

Use Contains for Membership Checks

At the simplest level, Contains answers "is this value present in that collection?" Inside a lambda query, that usually looks like this:

csharp
1using System;
2using System.Collections.Generic;
3using System.Linq;
4
5var allowedIds = new List<int> { 2, 4, 6 };
6var values = Enumerable.Range(1, 8);
7
8var filtered = values.Where(x => allowedIds.Contains(x)).ToList();
9Console.WriteLine(string.Join(", ", filtered));

This keeps only the values that appear in allowedIds. It is clear and idiomatic, which is why the pattern shows up everywhere in LINQ-heavy code.

Pick the Right Collection Type

For in-memory queries, the speed of Contains depends on the collection type. List<T>.Contains scans linearly, while HashSet<T>.Contains is usually much faster for repeated lookups.

csharp
1using System;
2using System.Collections.Generic;
3using System.Linq;
4
5var allowedIds = new HashSet<int> { 2, 4, 6, 8 };
6var values = Enumerable.Range(1, 10);
7
8var result = values.Where(x => allowedIds.Contains(x)).ToList();
9Console.WriteLine(string.Join(", ", result));

If you are filtering many items against a lookup set, converting the lookup collection to HashSet<T> is often the biggest optimization available.

Use Contains on Object Properties

A common real-world version is filtering objects by a property rather than by the object value itself.

csharp
1using System;
2using System.Collections.Generic;
3using System.Linq;
4
5record User(int Id, string Name);
6
7var users = new List<User>
8{
9    new User(1, "Ava"),
10    new User(2, "Liam"),
11    new User(3, "Noah"),
12};
13
14var selectedIds = new HashSet<int> { 1, 3 };
15var selectedUsers = users.Where(u => selectedIds.Contains(u.Id)).ToList();
16
17foreach (var user in selectedUsers)
18{
19    Console.WriteLine(user.Name);
20}

This is often the cleanest way to intersect an object sequence with a set of allowed keys.

How It Behaves in Entity Framework

When the query provider is Entity Framework or another remote LINQ provider, Contains is often translated into a database IN clause.

csharp
1var selectedIds = new List<int> { 10, 25, 40 };
2
3var query = dbContext.Users
4    .Where(u => selectedIds.Contains(u.Id))
5    .Select(u => new { u.Id, u.Email });

That is convenient, but it changes the cost model. Now you are not just doing a local membership test. You are sending a query to a database, and very large ID lists can become awkward for SQL translation, execution plans, or parameter limits.

For small and medium lists, this pattern is usually fine. For very large lists, staging values in a temp table or joining against a real table is often a better design.

Compose the Predicate Cleanly

Contains works well with other lambda conditions as long as you keep the predicate readable.

csharp
1var allowedRoles = new HashSet<string> { "Admin", "Editor" };
2
3var filtered = usersQuery
4    .Where(u => u.IsActive)
5    .Where(u => allowedRoles.Contains(u.Role))
6    .Where(u => u.SignInCount > 5);

Chaining focused Where calls is usually easier to understand and debug than packing every condition into one dense predicate.

Common Pitfalls

The first pitfall is using a List<T> for large in-memory membership checks and then wondering why the query slows down. If lookup performance matters, use HashSet<T>.

Another issue is assuming all LINQ providers treat Contains the same way. In-memory LINQ, Entity Framework, and other providers have different execution models.

Developers also sometimes put custom helper methods inside a query and expect the provider to translate them remotely. That often works in local LINQ but fails against databases.

Finally, very large membership lists can produce inefficient SQL even though the C# code looks elegant. Clear code is good, but you still need to understand the backend it targets.

Summary

  • 'Contains inside a LINQ lambda is the standard way to express membership filtering.'
  • For in-memory lookups, HashSet<T> is usually faster than List<T>.
  • Filtering on object properties with Contains is a common and clear pattern.
  • In Entity Framework, Contains often translates to SQL IN.
  • Large membership lists may require a different database-side design even when the LINQ code is correct.

Course illustration
Course illustration

All Rights Reserved.