Last Updated: May 22, 2026
LINQ stands for Language Integrated Query. It's a unified, type-safe query language built into C# that works the same way whether you're filtering an in-memory list, pulling rows from a SQL database, or walking an XML document. Instead of writing one syntax for collections, another for databases, and a third for XML, you write a single LINQ expression and a provider translates it to the right operations underneath. This lesson covers what LINQ is, the IEnumerable<T> foundation it sits on, the main providers in the ecosystem, and a first taste of what a LINQ query looks like.
Before LINQ shipped with C# 3.0, querying data meant learning a different API for every data source. Filter a List<Product>? Loop with foreach and a conditional. Filter a database table? Build a SQL string. Filter an XML document? Use XmlDocument and XPath. Each API had its own quirks, its own way of expressing "find me products under $50", and none of them were type-checked at compile time.
This works, but it's verbose. Every filter, sort, or projection means another loop and another temporary list. And the moment you switch from List<Product> to a database table, the whole pattern changes. You'd be writing SQL strings, mapping result rows by hand, and praying you got the column names right.
LINQ collapses all of that into one consistent shape. The same query expression filters a list, a SQL table, or an XML document. The provider underneath handles the translation; your code looks the same.
One line replaces the manual loop. Where is a LINQ operator that takes a predicate and returns the matching elements. The query reads top to bottom: "from products, give me the ones where price is under 50."
That's the surface change. The deeper change is that this pattern composes. You can chain Where, OrderBy, Select, GroupBy, and dozens of other operators together, and the same shape works whether products is a List<Product>, a Product[], or an Entity Framework DbSet<Product> backed by a database table.
Every LINQ query starts with a sequence. In C#, "sequence" means an IEnumerable<T>, the interface that says "I can be iterated one element at a time." It has exactly one method: GetEnumerator(). Anything that implements it can be the source of a LINQ query.
Most of the collection types in the Base Class Library (BCL, the standard library shipped with .NET) implement IEnumerable<T>. List<T>, T[], Dictionary<TKey, TValue>, HashSet<T>, Queue<T>, Stack<T>, even string (which is IEnumerable<char>). If you've got a collection, odds are you've got an IEnumerable<T>, and LINQ works on it out of the box.
The same Count operator works on a List<string>, a decimal[], a Dictionary<string, int>, and a string. None of those types know about each other; they only need to implement IEnumerable<T> for some T. LINQ takes care of the rest.
This is the LINQ-to-Objects flavor: you call LINQ operators on something that's already in memory, and the operators iterate through it using normal C# code. There's no magic, no translation step. Other LINQ providers handle the translation case (we'll get to those shortly), but LINQ-to-Objects is the common one and the one you'll use most.
LINQ-to-Objects operators are implemented as extension methods that wrap your collection in an iterator. Each operator in a chain adds one wrapper layer. For most code this overhead is invisible. In tight inner loops processing millions of elements, a plain for loop can still be faster, because LINQ's iterator protocol has per-element method call overhead that a direct loop doesn't.
LINQ operators live in the System.Linq namespace. Without that namespace imported, methods like Where, Select, and OrderBy aren't visible on your collections, even if you can see the types fine. They're not built into IEnumerable<T> directly; they're extension methods that the Enumerable class adds to it, and extension methods only show up when their namespace is in scope.
Add using System.Linq; and the same Where call compiles. The list type didn't change; the visible methods did.
Starting in .NET 6, System.Linq is one of the global usings the SDK adds by default to new projects. A global using is a using directive applied to every file in your project automatically, so you don't have to repeat it at the top of each file. You can see the active global usings in the auto-generated obj/Debug/{tfm}/{project}.GlobalUsings.g.cs file.
That convenience is why you'll often see modern C# code call .Where(...) without any visible using System.Linq; line. The directive is there; it's just hidden inside the project's global usings. Every example in this section shows using System.Linq; explicitly at the top, because seeing the namespace makes it obvious where the operators come from. In your own code on .NET 6 or later, you can leave it off if you're relying on the implicit global using.
LINQ isn't one library; it's a pattern. The C# compiler knows how to translate LINQ syntax into method calls (Where, Select, Join, and so on). Anything that exposes those methods can be a LINQ source. That "anything" is called a provider, and the same query syntax lights up on every one.
There are four main providers.
LINQ to Objects is the default. It operates on any IEnumerable<T> and runs in memory using the System.Linq.Enumerable static class. When you call .Where on a List<Product>, you're using LINQ to Objects. The operators are implemented as plain C# code that iterates your collection one element at a time. No translation, no SQL generation, no magic.
LINQ to Entities (also called LINQ to EF Core) operates on IQueryable<T>, which is IEnumerable<T>'s database-aware cousin. When you write dbContext.Products.Where(p => p.Price < 50m), EF Core doesn't iterate every row in memory. It builds an expression tree from your lambda, translates it to SQL, sends that SQL to the database, and only the matching rows come back over the wire. Same LINQ syntax, completely different execution model.
LINQ to XML operates on XDocument and XElement from System.Xml.Linq. Queries traverse the XML tree using familiar LINQ operators, returning sequences of elements, attributes, or projected values. It replaces the older XmlDocument and XPath APIs for most XML work.
Parallel LINQ (PLINQ) runs LINQ queries on multiple threads. You call .AsParallel() on the source and the rest of the operators parallelize their work across the thread pool. PLINQ is in System.Linq itself and works on IEnumerable<T>, with the same operator names. The _Parallel LINQ (PLINQ)_ lesson covers it in depth.
The diagram shows the shared shape. You write one LINQ query at the top. The provider underneath picks the right execution strategy for its data source. Your C# code doesn't change when the source does; the cost model does. A Where clause on a List<T> is a synchronous in-memory filter. The same clause on a DbSet<T> becomes a WHERE in the generated SQL. Same syntax, different physics.
For the rest of this section, every lesson focuses on LINQ to Objects unless noted otherwise. The operators, syntax rules, and execution model carry over to the other providers with provider-specific gotchas, but the foundation is the same everywhere.
Two more things are the provider model. First, providers can be written by third parties. MongoDB's official C# driver ships its own LINQ provider that translates queries into MongoDB's query language. Cosmos DB's SDK does the same against its document database. The same C# code you'd write against EF Core works against those drivers with only the source type changing. Second, the line between providers is the type of the source. IEnumerable<T> routes to LINQ to Objects. IQueryable<T> routes to whichever provider built that queryable. EF Core's DbSet<TEntity> implements IQueryable<TEntity>, which is how it hooks into the LINQ pipeline. If you call .AsEnumerable() on a queryable, you drop out of the queryable provider's world and back into LINQ to Objects, which forces the rest of the query to run client-side. That escape hatch matters for the next few lessons.
LINQ has two surface syntaxes that mean the same thing.
Method syntax is what you've already seen: chained method calls on a sequence, with lambda expressions for predicates and projections.
Query syntax looks more like SQL: a multi-line expression starting with from, ending with select or group, and reading roughly the way you'd describe the query in English.
Here's the same query in both, side by side. Filter customers whose name starts with "A", sort by name, and project to a string.
Both forms produce the same result. They compile to the same IL. The query-syntax form gets translated by the C# compiler into the method-syntax form before anything else happens. The C# spec calls this query expression translation, and you can read every translation rule in the language specification if you want the details.
Why have both? Query syntax reads more naturally for queries with joins, groupings, and let bindings (introducing intermediate variables). Method syntax is more flexible because some operators don't have a query-syntax keyword (like Count, First, Any, Distinct), and you have to drop to method syntax for those. Most C# developers learn both and pick whichever reads better for the query at hand.
This lesson stops at "they exist and they're equivalent." The _Query Syntax_ lesson covers query syntax in depth, including every keyword and how the translation works. The _Method Syntax_ lesson covers method syntax, lambdas, and how the operators chain. Both forms come back constantly through the rest of the section.
One claim worth pinning down. The pitch for LINQ leans heavily on "type-safe", and it's the single biggest reason engineers use it over raw SQL strings or hand-rolled loops. Type safety here means the C# compiler validates your query against the actual shape of your data at compile time, before the code ever runs.
Consider a SQL string built by hand:
The C# compiler is happy. The typo only surfaces at runtime when the database parses the query and reports a missing column. If the query lives behind a rarely-hit code path, the bug can sit in production for weeks before anyone notices.
The same intent in LINQ:
The compiler catches the typo immediately. There's no chance of shipping it. The same protection applies to operator return types, joining on mismatched key types, projecting to the wrong shape, anything the type system can see.
That protection survives the round trip through a database when you use LINQ to Entities. The compiler still checks p.Price against the Product class. EF Core, separately, validates that the property maps to a real database column at startup. Two layers of safety where the raw-SQL approach had none.
The rest of the LINQ section builds on this foundation, one focused lesson per topic.
| Lesson | Topic |
|---|---|
| LINQ Basics (this lesson) | LINQ Basics |
| Query Syntax | Query Syntax |
| Method Syntax | Method Syntax |
| Filtering & Ordering | Filtering & Ordering (Where, OrderBy, ThenBy) |
| Projection & Grouping | Projection & Grouping (Select, SelectMany, GroupBy) |
| Joins | Joins (Join, GroupJoin) |
| Aggregation Operators | Aggregation Operators (Sum, Average, Count, Aggregate) |
| Deferred vs Immediate Execution | Deferred vs Immediate Execution |
| Custom LINQ Operators | Custom LINQ Operators (yield return) |
| Parallel LINQ (PLINQ) | Parallel LINQ (PLINQ) |
Each lesson owns its scope cleanly. This lesson stays at the introduction level: what LINQ is, what IEnumerable<T> is, what the providers are, and what a query looks like at first glance. The deep mechanics, operator catalog, and execution semantics live in the lessons that follow.
A short orientation for what's coming. The _Query Syntax_ and _Method Syntax_ lessons cover the two surface syntaxes in detail, including the translation rules that turn query syntax into method calls. The _Filtering & Ordering_ through _Aggregation Operators_ lessons walk through the operator catalog grouped by purpose: filter and sort, project and group, join across sequences, aggregate to a single value. The _Deferred vs Immediate Execution_ lesson is the one that surprises most newcomers; LINQ queries are lazy by default, and learning when they actually execute is the difference between code that performs well and code that re-runs the same database query four times in one method. The _Custom LINQ Operators_ lesson shows how to write your own operators using yield return. The _Parallel LINQ (PLINQ)_ lesson introduces PLINQ, the parallel flavor.
The throughline across all ten lessons is the same IEnumerable<T> foundation, the same operator names, and the same compiler-checked query expressions. Once you know LINQ to Objects well, LINQ to Entities is mostly a matter of learning which operators translate cleanly to SQL and which don't.
A note on what this lesson deliberately leaves alone. There's no operator-by-operator walkthrough here. Where, Select, OrderBy, GroupBy, Join, Sum, Count, and the rest get full lessons of their own with examples, edge cases, and performance notes. There's no lambda expression syntax breakdown, no expression tree internals, no yield return mechanics. Those topics are central to other lessons and would crowd the basics if pulled forward. Same for deferred execution; LINQ queries don't actually run until you iterate them, which is the single most important behavior to remember, and the _Deferred vs Immediate Execution_ lesson spends a full lesson on it.
The takeaway from this lesson is the shape of the thing. LINQ is one query language layered on top of IEnumerable<T> (and IQueryable<T> for the database flavor), delivered via extension methods in System.Linq, expressible in either query syntax or method syntax, and powered by a small set of providers that handle the actual data access underneath.