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
IEnumerableexecute directly in the client application's memory. - Delegate-Based: LINQ methods on
IEnumerabletake delegates, specificallyFunc<T, bool>, as arguments. - Client-Side Filtering: If used with a database
context (such as Entity Framework), calling an
IEnumerablecauses 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
IQueryableare 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
IQueryabletake expression trees, specificallyExpression<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.