IQueryable vs IEnumerable: Which Is Faster in C#?


If you're working with C#, LINQ, and Entity Framework Core, you've probably seen code like this:


IQueryable<User> query = dbContext.Users;

IEnumerable<User> users = dbContext.Users;

Both allow you to use LINQ.

Both can filter, sort, and project data.

But they don't work the same way.

And when you're querying a database, choosing the wrong one can have a significant performance impact.

So which is faster?

For database queries, IQueryable is generally more efficient because it allows the query to be translated and executed on the database. IEnumerable performs LINQ operations in memory once the data has been loaded.

Let's see why.





The important difference isn't simply that one is "faster."

The real difference is where the work happens.


What is IEnumerable<T>?

IEnumerable<T> represents a sequence that can be enumerated.

For example:

var users = new List<User>
{
    new User { Id = 1, Name = "John" },
    new User { Id = 2, Name = "Sarah" },
    new User { Id = 3, Name = "David" }
};

IEnumerable<User> result = users
    .Where(u => u.Name.StartsWith("S"));

foreach (var user in result)
{
    Console.WriteLine(user.Name);
}

The filtering happens in your application.

Conceptually:

List<User>
    ↓
C# application
    ↓
Where(...)
    ↓
Filtered results

There is no database involved.


What is IQueryable<T>?

IQueryable<T> is designed for querying data sources that can translate LINQ expressions into another query language.

Entity Framework Core is a common example.

IQueryable<User> query = dbContext.Users;

var users = query
    .Where(u => u.Name.StartsWith("S"))
    .ToList();

EF Core can translate the LINQ expression into SQL.

Conceptually:

C# LINQ
   ↓
Expression Tree
   ↓
EF Core
   ↓
SQL
   ↓
Database
   ↓
Only matching rows

EF Core's query provider translates LINQ expressions into database-specific queries such as SQL.


The performance difference

Consider a database containing:

1,000,000 users

Suppose you only want users whose name starts with "S".

Approach 1 — filter using IQueryable

var users = dbContext.Users
    .Where(u => u.Name.StartsWith("S"))
    .ToList();

EF Core can translate the filter into SQL similar to:

SELECT *
FROM Users
WHERE Name LIKE 'S%'

The database performs the filtering.

Only matching rows need to be returned to your application.


Approach 2 — load everything first

Now consider:

var users = dbContext.Users
    .ToList()
    .Where(u => u.Name.StartsWith("S"));

The important part is:

.ToList()

Once ToList() executes, the database query has already been executed and the results have been loaded into memory.

The subsequent:

.Where(...)

runs against the in-memory collection.

The flow becomes:

Database
   ↓
1,000,000 rows
   ↓
Application memory
   ↓
Where(...)
   ↓
Matching users

That's potentially much more expensive.

EF Core specifically warns that unnecessary buffering with ToList or ToArray can load all results into memory when further LINQ operations could instead be performed before materialization.


The ToList() performance trap

This is one of the most important things to understand.

Compare:

Better

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

with:

Potentially expensive

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

They look similar.

They aren't.

First version

Database
   ↓
WHERE IsActive = 1
   ↓
Only required rows
   ↓
Application

Second version

Database
   ↓
All rows
   ↓
Application memory
   ↓
WHERE IsActive

If your table contains millions of rows, this difference can become significant.


So is IQueryable always faster?

No.

This is an important distinction.

You shouldn't think:

IQueryable = fast
IEnumerable = slow

That's not correct.

Instead think:

Where is the data, and where should the filtering happen?

If the data is in a database, you generally want the database to do as much filtering as possible.

If the data is already in memory, IEnumerable is perfectly appropriate.

For example:

var numbers = Enumerable.Range(1, 1_000_000);

IEnumerable<int> result = numbers
    .Where(x => x % 2 == 0);

There is no database here.

Using IQueryable doesn't magically make this faster.


The real performance rule

A useful rule is:

Filter, sort, project, and paginate before materializing database results whenever the database provider can translate those operations efficiently.

For example:

var users = await dbContext.Users
    .Where(u => u.IsActive)
    .OrderBy(u => u.Name)
    .Select(u => new
    {
        u.Id,
        u.Name
    })
    .Take(100)
    .ToListAsync();

This allows EF Core to build a database query that performs the filtering, sorting, projection, and limiting before returning the results.

This is usually much better than loading the entire table and doing everything in C#.


Another important difference: Select()

Consider:

var users = await dbContext.Users
    .Select(u => new
    {
        u.Id,
        u.Name
    })
    .ToListAsync();

You don't need every column from the database.

You're asking for only:

Id
Name

This is another important database optimization: retrieve only the columns you actually need. Microsoft recommends projecting only the required properties when querying efficiently with EF Core.


What happens when you call AsEnumerable()?

This is another common source of confusion.

Consider:

var users = dbContext.Users
    .Where(u => u.IsActive)
    .AsEnumerable()
    .Where(u => SomeCustomMethod(u));

Before AsEnumerable():

.Where(u => u.IsActive)

can be translated by EF Core and executed by the database.

After AsEnumerable():

.Where(u => SomeCustomMethod(u))

is performed in the application.

Conceptually:

Database
   ↓
WHERE IsActive = 1
   ↓
Application
   ↓
SomeCustomMethod()

This can be useful when you deliberately need client-side processing.

But don't accidentally move a large amount of work from the database to your application.

EF Core documents AsEnumerable() and ToList() as ways to explicitly opt into client-side evaluation; AsEnumerable() streams results, while ToList() buffers them in memory.


AsEnumerable() vs ToList()

These are also different.

AsEnumerable()

var users = dbContext.Users
    .Where(u => u.IsActive)
    .AsEnumerable();

This switches subsequent LINQ operations to LINQ-to-Objects.

It doesn't immediately create a list.

ToList()

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

This executes the query and buffers the results into a list.

So:

AsEnumerable()
    ↓
Switch to IEnumerable
    ↓
Results can be streamed

ToList()
    ↓
Execute query
    ↓
Buffer results in memory

If you don't need to materialize the entire result set, unnecessarily calling ToList() can increase memory usage.


What about IQueryable and deferred execution?

Both IEnumerable and IQueryable can use deferred execution.

For example:

var query = dbContext.Users
    .Where(u => u.IsActive);

At this point, you haven't necessarily retrieved the database rows yet.

The query is executed when you enumerate it or materialize it:

var users = query.ToList();

or:

foreach (var user in query)
{
    Console.WriteLine(user.Name);
}

This is why IQueryable is useful for dynamically building database queries.

You can keep composing:

var query = dbContext.Users
    .Where(u => u.IsActive);

if (!string.IsNullOrEmpty(search))
{
    query = query.Where(u => u.Name.Contains(search));
}

query = query.OrderBy(u => u.Name);

var users = await query.ToListAsync();

The final query can then be translated by EF Core and sent to the database.


A common mistake in repository methods

Consider:

public IEnumerable<User> GetUsers()
{
    return dbContext.Users;
}

This may hide the fact that the caller can continue composing the query.

For example:

var users = repository.GetUsers()
    .Where(u => u.IsActive)
    .ToList();

Depending on the return type and implementation, you may lose the ability to compose provider-specific queries efficiently.

If you're deliberately exposing a composable database query, you might instead expose:

public IQueryable<User> GetUsers()
{
    return dbContext.Users;
}

However, this doesn't mean every repository should return IQueryable.

There are architectural trade-offs around exposing database query providers from repositories, and the right choice depends on the design of your application.


When should you use IQueryable?

Use IQueryable when:

  • you're querying a database

  • the query needs dynamic composition

  • filtering should happen on the database

  • you need database-side sorting

  • you need database-side pagination

  • you want EF Core to translate LINQ into SQL

Example:

IQueryable<Product> query = dbContext.Products;

query = query.Where(p => p.IsAvailable);

query = query
    .OrderBy(p => p.Name)
    .Take(50);

var products = await query.ToListAsync();

When should you use IEnumerable?

Use IEnumerable when:

  • the data is already in memory

  • you're working with arrays or lists

  • database translation isn't needed

  • you intentionally want client-side processing

  • the dataset is small enough that in-memory processing is appropriate

Example:

var products = new List<Product>();

IEnumerable<Product> result = products
    .Where(p => p.IsAvailable)
    .OrderBy(p => p.Name);

Which one is faster?

Here's the practical answer:

ScenarioBetter choice
Filtering 1 million database rowsIQueryable
Filtering 100 in-memory objectsIEnumerable
Database paginationIQueryable
Database sortingIQueryable
Database projectionIQueryable
Processing an existing List<T>IEnumerable
Running custom C# logic that can't be translatedIEnumerable
Dynamic EF Core queriesIQueryable

So don't ask:

"Is IQueryable faster than IEnumerable?"

Ask:

"Where should this operation execute?"

That's the real performance question.


The biggest mistake to avoid

Don't do this:

var users = dbContext.Users
    .ToList()
    .Where(u => u.IsActive)
    .OrderBy(u => u.Name)
    .Take(100);

You're doing the filtering, sorting, and pagination after loading the data.

Prefer:

var users = await dbContext.Users
    .Where(u => u.IsActive)
    .OrderBy(u => u.Name)
    .Take(100)
    .ToListAsync();

Now the database can perform those operations before returning the result.


One more performance consideration: indexes

Even using IQueryable doesn't automatically make a query fast.

For example:

var users = await dbContext.Users
    .Where(u => u.Email == email)
    .ToListAsync();

If the database has to scan a huge table because there isn't an appropriate index, the query can still be slow.

The database's execution plan, indexes, joins, result size, and generated SQL all matter.

Microsoft's EF Core performance guidance emphasizes that appropriate indexes are often one of the most important factors determining query performance.

So:

IQueryable
    ≠
Automatically fast

Instead:

IQueryable
    +
Good SQL
    +
Proper indexes
    +
Small result set
    +
Efficient projection
    =
Better database performance

Final takeaway

IQueryable and IEnumerable aren't competing versions of the same thing.

They solve different problems.

Use IQueryable

when your query should be executed by the underlying provider, such as EF Core and a database.

Use IEnumerable

when you're working with data that is already in memory.

The biggest performance mistake is usually not choosing the "wrong interface."

It's materializing data too early.

This:

dbContext.Users
    .Where(...)
    .OrderBy(...)
    .Take(100)
    .ToListAsync();

is usually a much better pattern than:

dbContext.Users
    .ToList()
    .Where(...)
    .OrderBy(...)
    .Take(100);

When working with large datasets, keeping the query composable until you actually need the results can prevent unnecessary database transfers, CPU work, and memory usage.


Quick rule to remember

Database → IQueryable → filter/sort/project/paginate → ToList()

Memory → IEnumerable → LINQ-to-Objects

Once you understand where the query executes, the IQueryable vs IEnumerable performance question becomes much easier to answer.


Comments

Popular posts from this blog

How to Install .NET 10 SDK on Windows, Linux, and macOS

Microsoft Agent Framework Explained: A .NET Developer’s Architecture Guide

Why Is Your ASP.NET Core API Using So Much Memory? 7 Causes and Fixes