You've been writing LINQ to Objects since Intermediate Part IV. This lesson just gives it its name — and the family it belongs to.
Quick question: what is "LINQ," exactly? If your answer is "Where, Select, OrderBy, that kind of thing" — you're not wrong, but you're only seeing one piece of something bigger. "LINQ" was never the name of a single technology. It's the name of a pattern: one consistent query syntax and method syntax, reused across several completely different execution engines, each called a provider. Every LINQ query you've ever written — from Intermediate's first Where lambda, through this Part's deep dive into IEnumerable<T> and IQueryable<T> — was really a query against one specific provider, and until now, that provider mostly didn't need a name.
This lesson gives it one: LINQ to Objects — LINQ operators run against any in-memory IEnumerable<T>, executed as pure, compiled C# delegates, with no expression tree involved unless you explicitly opt in. You'll see exactly how it fits alongside LINQ to Entities (EF Core, next lesson) and the historical names — LINQ to XML, LINQ to SQL — you'll encounter in the wild.
LINQ (Language-Integrated Query) is a single, unified syntax for querying — filtering, sorting, projecting, grouping — that C# lets you write consistently, no matter what you're querying. LINQ to Objects is what you get when the thing being queried is a plain in-memory collection: an array, a List<T>, a Dictionary<TKey,TValue>, or any custom type implementing IEnumerable<T> (including one you wrote yourself with yield return, from earlier in this Part). It's the provider you reach for the moment your data already lives in your application's own memory.
LINQ to Objects is the set of extension methods defined on IEnumerable<T> by the System.Linq.Enumerable static class — Where, Select, OrderBy, GroupBy, and every other operator you've used throughout this course. Each of those methods accepts a plain Func<T,...> delegate, not an Expression<TDelegate> — meaning the C# compiler turns your lambda straight into executable IL, exactly like any other method call, with no intermediate data structure describing the query. There is nothing to "translate," because the code that runs is the query.
List<int> numbers = [1, 2, 3, 4, 5, 6];
// This is LINQ to Objects: numbers is IEnumerable<int>, the lambda
// compiles straight to a Func<int, bool> delegate, and Where(...)
// runs that delegate directly against each in-memory item.
var evens = numbers.Where(n => n % 2 == 0);Before LINQ existed (C# 2.0 and earlier), querying an in-memory list meant hand-written loops. Querying a database meant raw SQL strings and a completely different API (ADO.NET's SqlCommand, readers, and so on). Querying XML meant yet another API, with its own navigation methods. Three different data sources, three unrelated skill sets, none of which transferred to the others — a developer moving from filtering a list to filtering a database table had to reach for an entirely different toolbox.
LINQ's actual insight was making the query syntax itself reusable, while letting each data source supply its own provider — the engine that actually executes the query, in whatever way makes sense for that data source. Write .Where(x => x.Price > 20) against a List<Product>, and LINQ to Objects runs it as a delegate, in memory. Write the exact same-looking .Where(x => x.Price > 20) against context.Products, and LINQ to Entities (the next lesson) turns it into a SQL WHERE clause instead. Same syntax you already know either way — the provider decides what happens underneath.
LINQ is the umbrella name. Underneath it sit several providers, each targeting a different kind of data source:
| Provider | Targets | Executes as | Status |
|---|---|---|---|
| LINQ to Objects | Any in-memory IEnumerable<T> — arrays, List<T>, custom iterators | Compiled C# delegates (Func<T,...>), no expression tree | What this lesson covers — and what you've been doing all along |
| LINQ to Entities | An EF Core DbSet<T> / IQueryable<T>, backed by a real database | An Expression tree, translated into SQL by EF Core's provider | Covered in depth in the next lesson |
| LINQ to XML | XDocument / XElement trees | In-memory traversal over a loaded XML document | Still available, real, and occasionally seen in the wild — not taught in depth here |
| LINQ to SQL | SQL Server specifically, via its own separate O/RM | Translated to SQL, much like LINQ to Entities | Legacy — effectively superseded by EF Core; recognize the name in older code, don't reach for it in new projects |
Every one of these shares the same Where/Select/OrderBy vocabulary and the same query-syntax option (from x in source select x) — that consistency is the entire point of calling it all "LINQ." What differs, provider to provider, is what happens the moment you ask for results.
.Where(...) (or any other operator) is called.System.Linq.Enumerable's extension methods. Your lambda compiles directly to a Func<T,...> delegate — ordinary, executable IL.System.Linq.Queryable's extension methods, and your lambda compiles to an Expression<TDelegate> — data describing the query, not executable code. What happens with that expression tree from there depends entirely on which IQueryable<T> implementation you're using — EF Core's, for LINQ to Entities.DbSet<T> with IQueryable<T> operators, call .ToList(), and every LINQ operator chained after that point runs as LINQ to Objects against the now-in-memory List<T> — the exact "IQueryable vs IEnumerable" seam this Part's earlier lessons already introduced mechanically.This is nothing new mechanically — it's every LINQ query you've written since Intermediate's Filtering lesson. What's new is naming what's actually happening:
public record Product(int Id, string Name, decimal Price, int Stock);
List<Product> products =
[
new(1, "Wireless Mouse", 24.99m, 40),
new(2, "Standing Desk", 349.00m, 0),
new(3, "Desk Lamp", 19.50m, 15),
];
// LINQ to Objects, start to finish:
// - 'products' is List<Product>, which implements IEnumerable<Product>
// - Where(...) and OrderBy(...) resolve to System.Linq.Enumerable
// - each lambda compiles to a Func<Product, ...> delegate — ordinary IL
// - execution happens entirely inside THIS process, over the List<T> already in memory
var affordable = products
.Where(p => p.Price < 50 && p.Stock > 0)
.OrderBy(p => p.Price);
foreach (var p in affordable)
Console.WriteLine($"{p.Name}: {p.Price:C}");
// Desk Lamp: $19.50
// Wireless Mouse: $24.99Code → Meaning → Result: Nothing about running this code involves a database, a network call, or a translation step of any kind — products was already sitting in memory before the query even started, and it stays there the whole time. That's the entire definition of LINQ to Objects: the provider is "your own process, executing compiled delegates."
// Query syntax — compiles down to the exact same Enumerable method calls
var affordable =
from p in products
where p.Price < 50 && p.Stock > 0
orderby p.Price
select p;The C# compiler rewrites query syntax into the equivalent method-syntax calls before anything else happens — so this is the identical LINQ to Objects query as above, just spelled differently. The provider is determined by the source's type (IEnumerable<T> vs. IQueryable<T>), never by which syntax you choose to write.
A background service polls a payment gateway, gets back a batch of transactions as a plain List<Transaction> (already fully in memory — it came from a JSON HTTP response, not a database), and needs to summarize suspicious activity before writing an alert:
public record Transaction(int AccountId, decimal Amount, string Country, DateTime OccurredAt);
public IEnumerable<Transaction> FindSuspicious(List<Transaction> batch)
{
// batch is already in memory — this is LINQ to Objects end to end.
// There's no database connection here to translate anything into.
return batch
.Where(t => t.Amount > 5000m)
.Where(t => t.Country != "US")
.OrderByDescending(t => t.Amount);
}Contrast this with the very next lesson's example, where the near-identical filter runs against context.Transactions instead — same-looking C# code, but this time IQueryable<T> and a SQL translation step are involved. Recognizing which one you're looking at — by checking whether the source is an in-memory collection or a DbSet<T> — is exactly the skill this lesson is building.
Imagine one universal phrase — "please sort these by date" — that you can say to a librarian standing in front of a shelf of books, or phone to a records office across town. Said to the librarian standing right there, they just pick up each book and physically reorder them on the shelf, right now, themselves — that's LINQ to Objects: the work happens right where the data already is, using the simplest, most direct mechanism available. Said over the phone to the records office, your words get relayed, reinterpreted into their internal filing procedure, and carried out by their own staff, in their own building, using their own system — that's LINQ to Entities: the same phrase, but translated and executed somewhere else entirely, by a different set of rules.
The phrase never changes. What changes is who's listening, and what they do with it.
public static IEnumerable<T> Where<T>(this IEnumerable<T> source, Func<T, bool> predicate) — the lambda you write is compiled by the C# compiler into IL for an actual, callable method (a delegate), exactly like any other method reference. It can only ever be run, never inspected as data.public static IQueryable<T> Where<T>(this IQueryable<T> source, Expression<Func<T, bool>> predicate) — because the parameter type is Expression<TDelegate>, the C# compiler does something entirely different: it builds a tree of objects describing the lambda's structure — "a member access, then a comparison, then a constant" — instead of compiling it to runnable code. This is the expression-tree machinery covered mechanically earlier in this Part.List<T> implements only IEnumerable<T>, so Where(...) resolves to Enumerable.Where — a delegate. DbSet<T> implements IQueryable<T> (which itself extends IEnumerable<T>), so when the variable's static type is IQueryable<T>, Where(...) resolves to Queryable.Where instead — an expression tree. Same method name, same-looking lambda, genuinely different compiled output.Expression<Func<Product, bool>> predicate = p => p.Price > 20; even for in-memory data — the "advanced generics/delegates" material from earlier in this Advanced book covered how expression trees themselves work — but nothing about ordinary LINQ to Objects code does this by default. It's a deliberate opt-in, not something that happens silently along the way.They share syntax, not execution. LINQ to Objects runs compiled C# in your own process. LINQ to Entities and LINQ to SQL both translate an expression tree into SQL and run it on a database server — an entirely different machine, in an entirely different language, with an entirely different set of what's even expressible (the next two lessons cover exactly that boundary). The name "LINQ" describes the shared query vocabulary — it says nothing about where or how the query actually executes.
Nearly every LINQ query in Intermediate — filtering a List<Employee>, grouping a report by category, projecting a collection of orders into DTOs — was LINQ to Objects, whether or not the name came up. The provider was decided the moment you queried a plain IEnumerable<T>; you just hadn't been given the formal vocabulary for it yet. This lesson doesn't teach a new skill — it names a skill you already have.
Believing context.Products.ToList().Where(...) is still LINQ to Entities because the data started life in a database. Once .ToList() runs, the result is a plain List<Product> — every operator chained after it is LINQ to Objects, executed entirely in memory, with no further translation happening. Track the provider by the current static type at each point in the chain, not by where the data originally came from.
Calling EF Core "LINQ to SQL" in conversation or documentation — they're historically distinct technologies: LINQ to SQL was Microsoft's earlier, SQL-Server-only O/RM, now legacy; LINQ to Entities is EF Core's LINQ provider, actively developed, and provider-agnostic (SQL Server, PostgreSQL, SQLite, and others). Use "LINQ to Entities" (or simply "EF Core's LINQ provider") for current work — recognize "LINQ to SQL" only as a name you might see in an older codebase or article.
.ToList().IQueryable<T> (LINQ to Entities) lets filtering happen server-side, before anything crosses the network.XDocument/XElement — that's LINQ to XML, a related but separate provider not covered in depth here.IEnumerable<T>, or is it an IQueryable<T> still waiting to be translated and sent elsewhere?" The answer names the provider — and tells you exactly what kind of execution model you're actually working with.
IEnumerable<T> — compiled delegates, no translation, runs in your own process.IQueryable<T> — expression trees, translated to SQL, runs on the database.IEnumerable<T>, running as compiled Func<T,...> delegates — no expression tree involved unless explicitly requested.IQueryable<T> — expression trees translated into SQL and run on the database.You've just given a name to something you've been doing since Intermediate Part IV. Let's confirm the organizing structure stuck.
1. What is the most accurate way to describe what "LINQ" refers to?
Correct: B
Why B is correct: As covered in What Is It? and Big Picture, LINQ is the umbrella name for one consistent query syntax that multiple providers — LINQ to Objects, LINQ to Entities, LINQ to XML, and others — each implement with their own execution model underneath.
Why A is incorrect: LINQ predates and extends beyond database querying entirely — LINQ to Objects, the subject of this lesson, has nothing to do with databases.
Why C is incorrect: EF Core is one specific implementation of one specific LINQ provider (LINQ to Entities) — it isn't a synonym for LINQ as a whole.
Why D is incorrect: In-memory collections are exactly what LINQ to Objects targets, but that's only one provider among several — LINQ itself is broader than any single one of them.
Reinforcement: Think "LINQ" the pattern, "LINQ to X" the specific provider that actually executes a given query.
2. A developer writes someList.Where(x => x.IsActive), where someList is a List<Customer>. What determines that this is LINQ to Objects, rather than LINQ to Entities?
Correct: B
Why B is correct: As explained in Under the Hood, the deciding factor is the static type of the source — List<T> only implements IEnumerable<T>, so overload resolution picks Enumerable.Where, which takes a plain delegate, making this LINQ to Objects by construction.
Why A is incorrect: The shape of the lambda's body has no bearing on which provider handles the query — the same lambda syntax works identically for either provider; what differs is how it's compiled underneath.
Why C is incorrect: async/await is unrelated to which LINQ provider a query uses — LINQ to Objects and LINQ to Entities both work fine in synchronous or asynchronous code.
Why D is incorrect: This is exactly the mistake called out in Common Mistakes — once data is materialized into a plain List<T>, its original source is irrelevant; only its current static type decides the provider.
Reinforcement: Always check the current static type of the LINQ source at each step — that alone tells you which provider is in play.
3. Which statement correctly distinguishes LINQ to Objects from LINQ to Entities in terms of what the compiler generates for a lambda passed to Where(...)?
Correct: A
Why A is correct: As detailed in Under the Hood, this is the actual fork in the road — Enumerable's methods take Func<T,...> parameters (compiled, runnable delegates), while Queryable's methods take Expression<Func<T,...>> parameters (a tree of objects describing the lambda's structure, inspectable and translatable).
Why B is incorrect: The compiled output genuinely differs at compile time, based on overload resolution — it's not a runtime distinction that only shows up later.
Why C is incorrect: Neither provider has any inherent relationship to async/await — that's an unrelated language feature.
Why D is incorrect: This reverses the actual mapping — it's LINQ to Entities (via IQueryable<T>) that uses expression trees, and LINQ to Objects (via IEnumerable<T>) that uses plain delegates.
Reinforcement: Func<T,...> = code that runs. Expression<Func<T,...>> = data describing code, for something else to interpret and translate.
4. Which of these is still a real, currently-used LINQ provider, as opposed to a legacy name mainly worth recognizing in older code?
Correct: B
Why B is correct: As covered in Big Picture, LINQ to XML remains available and occasionally used for querying XDocument/XElement trees — it isn't taught in depth in this course, but it's a live, current part of .NET, unlike LINQ to SQL.
Why A is incorrect: LINQ to SQL is specifically called out as legacy — effectively superseded by EF Core (LINQ to Entities) for new development, though the name still appears in older codebases and articles.
Why C is incorrect: LINQ to XML is not described as legacy in this lesson — only LINQ to SQL is.
Why D is incorrect: The Big Picture table lists four provider names explicitly, including LINQ to XML and LINQ to SQL — the ecosystem is broader than just the two providers this course covers in depth.
Reinforcement: Recognizing a provider's name and status (current vs. legacy) is useful even without studying it in depth — it helps you correctly interpret code and documentation you encounter later.
You now have the formal vocabulary for something you've been practicing all along. Next up: a genuine deep dive into the provider you'll use most in production — LINQ to Entities, and exactly how EF Core turns your query into SQL.
dotnetmadeeasy.com — Learn C# and .NET, the right way.