LINQ (Language Integrated Query) lets you write query expressions directly in C# to filter, sort, and transform data from collections, databases, and XML. There are two equivalent syntaxes: query syntax resembles SQL with from, where, select, and orderby clauses, while method syntax uses extension methods like Where, Select, and OrderBy chained with dot notation. Both compile to the same IL code, so the choice is mostly stylistic. Most LINQ operators return IEnumerable, and a key characteristic is deferred execution: a query is not executed when defined, but only when its results are enumerated, such as inside a foreach loop, a ToList() call, or a Count() call. The yield return statement enables iterator methods that produce values one at a time, lazily, with the compiler generating a state machine and yield break terminating the iterator early. IEnumerable is the base interface for all generic collections that can be enumerated and is what enables both foreach and LINQ.
The core operators form a small, composable vocabulary. Select projects each element into a new form, essentially a map operation. Where filters elements based on a predicate. OrderBy and ThenBy produce a sorted sequence. GroupBy groups elements by a key, returning IEnumerable> where each group exposes its Key and the contained elements. Aggregate applies an accumulator function over a sequence, similar to reduce or fold in functional languages, and is useful for custom accumulation beyond what Sum and Count provide. Cast attempts to cast every element to T and throws on the first mismatch, while OfType keeps only elements of the target type, skipping the rest, which is the right tool when filtering a non-generic legacy collection.
LINQ also supports more advanced shapes. Join pairs elements by a key, like SQL's INNER JOIN, while Zip pairs elements positionally and stops at the shorter sequence. SelectMany flattens nested sequences: orders.SelectMany(o => o.Items) returns a single IEnumerable- , while orders.Select(o => o.Items) returns IEnumerable>, which is rarely what you want. SelectMany is essential for one-to-many projections and chaining inner queries.
A critical distinction is between IEnumerable and IQueryable. IEnumerable represents an in-memory sequence where filtering happens client-side, while IQueryable builds an expression tree that can be translated to another query language (typically SQL) and executed server-side, often with significant performance benefits when working with databases; the same method syntax is used in both cases, but the execution location is fundamentally different. For materializing results, ToList returns a mutable List, while ToArray returns a fixed-size T[]; both force enumeration and are O(n). For single-element queries, First throws on an empty sequence, FirstOrDefault returns default(T), Single throws on zero or more than one element, and SingleOrDefault returns default(T) only if the sequence is empty; use Single when you expect exactly one match.