C#  

Difference Between IEnumerable and IQueryable in C#

Understanding the difference between IEnumerable and IQueryable is important when working with LINQ, Entity Framework Core, and data-intensive applications. Both are used for querying data, but they serve different purposes and can affect how queries are processed and executed.

A common misconception is that IEnumerable always loads data into memory and IQueryable always executes queries in the database. The reality is more nuanced and depends on the underlying data provider and when the query is materialized.

What is IEnumerable?

IEnumerable<T> is an interface that represents a sequence of objects that can be enumerated.

Key characteristics:

  • Supports iteration over a collection.

  • Commonly used with in-memory collections.

  • LINQ operations are performed in memory when working with materialized data.

  • Suitable for scenarios where data is already available in memory.

Example:

var data = context.Users.ToList(); // Query executed here
var result = data.Where(x => x.Name.StartsWith("A"));

In this example, ToList() materializes the query and loads all records into memory. The Where() filter is then applied in memory.

What is IQueryable?

IQueryable<T> extends IEnumerable<T> and provides support for expression trees.

This allows query providers such as Entity Framework Core to translate LINQ expressions into database-specific queries, such as SQL.

Key characteristics:

  • Inherits from IEnumerable<T>.

  • Supports expression trees.

  • Enables query translation by a provider.

  • Uses deferred execution until the query is materialized.

  • Ideal for database queries involving filtering, sorting, and pagination.

Example:

var result = context.Users
    .Where(x => x.Name.StartsWith("A"));

At this point, no database query has been executed. Entity Framework builds an expression tree that can later be translated into SQL.

The query executes when it is materialized:

var users = result.ToList();

Important Clarification

One of the most important things to understand is that IQueryable<T> inherits from IEnumerable<T>.

IQueryable<User> query = context.Users;

IEnumerable<User> users = query;

This is perfectly valid because every IQueryable<T> is also an IEnumerable<T>.

The difference is not the interface itself. The difference comes from:

  • The underlying query provider.

  • Whether the query has been materialized.

  • Where the LINQ operations are executed.

For example:

var users = context.Users
                   .ToList()
                   .AsQueryable();

Although users is now an IQueryable<User>, the data has already been loaded into memory. Any additional filtering will happen in memory rather than being translated into SQL.

This demonstrates that execution behavior depends on the provider and materialization process, not solely on the interface type.

Key Differences Between IEnumerable and IQueryable

FeatureIEnumerableIQueryable
PurposeEnumerating collectionsBuilding provider-translatable queries
Expression Tree SupportNoYes
Query TranslationNot supportedSupported
Typical UsageIn-memory collectionsDatabase queries
ExecutionIn-memory after materializationDeferred until materialization
Best ForSmall or already-loaded datasetsFiltering and querying large datasets

Execution Behavior Explained

IEnumerable Execution Flow

  1. Query executes and data is loaded.

  2. Data is stored in memory.

  3. LINQ operations are performed in memory.

Example:

var users = context.Users.ToList();
var filtered = users.Where(u => u.Age > 25);

Here, all users are loaded first, and filtering occurs afterward in memory.

IQueryable Execution Flow

  1. Build an expression tree.

  2. Query provider translates the expression.

  3. Execute query when materialized.

  4. Return only the required data.

Example:

var filtered = context.Users
    .Where(u => u.Age > 25)
    .ToList();

Entity Framework translates the filter into SQL and retrieves only matching records.

Performance Comparison

Less Efficient Approach

var users = context.Users.ToList();
var filtered = users.Where(u => u.Age > 25);

Potential issues:

  • Loads all records into memory.

  • Higher memory consumption.

  • Unnecessary data transfer.

More Efficient Approach

var filtered = context.Users
    .Where(u => u.Age > 25)
    .ToList();

Benefits:

  • Filtering occurs in the database.

  • Reduced memory usage.

  • Smaller result set transferred to the application.

When to Use IEnumerable

Use IEnumerable when:

  • Data is already loaded into memory.

  • Working with collections such as arrays or lists.

  • Performing operations that cannot be translated by the database provider.

  • Dataset size is relatively small.

When to Use IQueryable

Use IQueryable when:

  • Working with Entity Framework Core.

  • Querying databases.

  • Applying filtering, sorting, grouping, or pagination.

  • Working with large datasets.

  • You want the provider to optimize query execution.

Common Mistakes to Avoid

Calling ToList() Too Early

var users = context.Users.ToList()
                         .Where(u => u.IsActive);

This loads all records before filtering.

Prefer:

var users = context.Users
                   .Where(u => u.IsActive)
                   .ToList();

Assuming IQueryable Always Means Database Execution

var users = context.Users
                   .ToList()
                   .AsQueryable();

Although the variable type is IQueryable, the data is already in memory.

Mixing Materialized and Non-Materialized Queries

Be aware of where query execution occurs and avoid unnecessary materialization.

Real-World Scenario

A common pattern in production applications is:

var users = context.Users
    .Where(u => u.IsActive)
    .OrderBy(u => u.Name)
    .ToList();

This allows Entity Framework to generate optimized SQL while retrieving only the data required by the application.

Conclusion

IEnumerable and IQueryable are both important interfaces in .NET, but they serve different purposes. IEnumerable<T> represents an enumerable sequence, while IQueryable<T> extends it by enabling query providers to translate expressions into optimized queries.

The key distinction is not that one interface inherently executes in memory and the other in the database. Instead, execution behavior depends on the underlying provider and when the query is materialized. When working with Entity Framework and databases, keeping queries as IQueryable until the final materialization step typically results in better performance and more efficient data access.