IEnumerable vs IQueryable in C# LINQ Explained

Understanding the difference between IEnumerable and IQueryable is essential for writing efficient, high-performance data access code in C# and LINQ. While both interfaces represent forward-only collections of data and support LINQ query operations, they execute queries differently. This article breaks down how each interface works under the hood, examines their execution mechanisms, and outlines best practices for choosing the right one for your application.

Understanding IEnumerable

IEnumerable resides in the System.Collections (and generic System.Collections.Generic) namespace. It is the base interface for all non-generic and generic collections in .NET, such as List<T>, Array, and Dictionary<TKey, TValue>.

When you use LINQ extension methods on an IEnumerable collection, you are using LINQ to Objects.

Key Characteristics:

  • In-Memory Execution: Queries defined on IEnumerable execute directly in the client application's memory.
  • Delegate-Based: LINQ methods on IEnumerable take delegates, specifically Func<T, bool>, as arguments.
  • Client-Side Filtering: If used with a database context (such as Entity Framework), calling an IEnumerable causes the database to execute the query as written up to that point, return the entire result set across the network, and perform subsequent filtering, sorting, or pagination entirely in the application's local memory.

Understanding IQueryable

IQueryable resides in the System.Linq namespace and inherits from IEnumerable. It is designed to evaluate queries against external data sources, such as relational databases, web services, or custom data providers.

When you use LINQ extension methods on an IQueryable collection, you are typically using LINQ to Entities or another query provider.

Key Characteristics:

  • Server-Side Execution: Queries defined on IQueryable are converted into the native query language of the underlying provider (such as SQL) and executed directly on the data server.
  • Expression Tree-Based: LINQ methods on IQueryable take expression trees, specifically Expression<Func<T, bool>>, as arguments. The query provider parses this expression tree at runtime to generate the target query.
  • Optimized Data Retrieval: Only the data that matches the final composed query is transmitted across the network from the database to your application.

Key Differences

Feature IEnumerable<T> IQueryable<T>
Namespace System.Collections.Generic System.Linq
Target Data In-memory collections External data sources (SQL, APIs)
Execution Location Client-side (in application memory) Server-side (database engine)
Query Representation Compiled delegates (Func<T, bool>) Expression trees (Expression<Func<T, bool>>)
Paging & Filtering Retrieves all data first, filters locally Translates filters into SQL before retrieval
Lazy Evaluation Yes (via iteration) Yes (via deferred execution)

Practical Scenario: Query Filtering and Pagination

Consider an Entity Framework Core query on a table containing 100,000 users where you only need the first 10 active records:

Using IEnumerable

IEnumerable<User> users = dbContext.Users;
var activeUsers = users.Where(u => u.IsActive).Take(10).ToList();

In this scenario, dbContext.Users cast to IEnumerable causes the runtime to load all 100,000 rows from the database into memory. The Where and Take operations then filter down to the 10 records client-side. This results in heavy memory usage, network congestion, and slow performance.

Using IQueryable

IQueryable<User> users = dbContext.Users;
var activeUsers = users.Where(u => u.IsActive).Take(10).ToList();

In this scenario, the Where and Take calls modify the internal expression tree. When .ToList() triggers execution, the provider translates the whole expression into SQL:

SELECT TOP (10) * FROM [Users] WHERE [IsActive] = 1;

Only 10 records are transferred from the database to application memory.

When to Use Each Interface

Use IQueryable when:

  • Querying external databases or remote services.
  • Implementing pagination, server-side filtering, or sorting.
  • Composing queries conditionally before sending a single, optimized query to the data store.

Use IEnumerable when:

  • Working with data that is already loaded into local memory (arrays, lists).
  • Executing custom C# methods or logic within queries that a SQL provider cannot translate into valid database expressions.
  • Building simple, in-memory collection transformations.