LINQ
LINQ (Language Integrated Query) is a set of extension methods on IEnumerable<T> (LINQ to Objects) and IQueryable<T> (LINQ providers such as Entity Framework) plus a query syntax built into the language.
Concept
LINQ (Language Integrated Query) is a set of extension methods on IEnumerable<T> (LINQ to Objects) and IQueryable<T> (LINQ providers such as Entity Framework) plus a query syntax built into the language.
Query Syntax vs Method Syntax Both compile to the same method calls. Method syntax covers every operator; query syntax is nicer for joins and let.
Where, OfType<T> Select, SelectMany (flatten) OrderBy, OrderByDescending, ThenBy, ThenByDescending, Order, Reverse
GroupBy, ToLookup, Chunk, CountBy, AggregateBy Join (inner), GroupJoin (left outer), Zip, LeftJoin (.NET 10)
Count, LongCount, Sum, Average, Min, Max, MinBy, MaxBy, Aggregate
First, FirstOrDefault, Single, SingleOrDefault, Last, LastOrDefault, ElementAt
Distinct, DistinctBy, Union, Intersect, Except Any, All, Contains
Take, Skip, TakeWhile, SkipWhile, TakeLast ToList, ToArray, ToDictionary, ToHashSet, ToLookup Deferred vs Immediate Execution
Most operators (Where, Select, OrderBy, …) are deferred: they build a query that runs only when enumerated (foreach, ToList). Each enumeration re-runs it against the *current* data. Aggregates, element operators and To… conversions execute immediately.
First throws if nothing matches, FirstOrDefault returns default; Single throws if there is not exactly one match.
var q1 = from e in ecus where e.Voltage < 12.5 orderby e.Voltage select e.Code; // query syntax
var q2 = ecus.Where(e => e.Voltage < 12.5).OrderBy(e => e.Voltage).Select(e => e.Code); // method syntaxWhy It Matters
LINQ replaces hand-written loops for filtering, grouping, joining and summarizing data — test results, logs, CAN traces, database rows (through EF Core) — with concise, declarative, composable code.
Syntax
source.Where(x => cond).Select(x => proj).OrderBy(x => key).ToList();
from x in source where cond orderby key select proj
source.GroupBy(x => key).Select(g => new { g.Key, Count = g.Count() });
outer.Join(inner, o => o.Key, i => i.Key, (o, i) => result);Example
using System.Globalization;
CultureInfo.CurrentCulture = CultureInfo.InvariantCulture;
var ecus = new List<Ecu>
{
new("VCU", "V1", 0, 12.6), new("BMS", "V1", 3, 12.4), new("ABS", "V1", 1, 12.7),
new("VCU", "V2", 2, 12.1), new("BMS", "V2", 0, 12.5), new("EPS", "V3", 5, 11.9),
};
var vehicles = new List<Vehicle> { new("V1", "Aurora", 2025), new("V2", "Hauler", 2023), new("V4", "Spark", 2026) };
// Filtering + projection + sorting (method syntax)
var faulty = ecus.Where(e => e.FaultCount > 0)
.OrderByDescending(e => e.FaultCount).ThenBy(e => e.Code)
.Select(e => $"{e.Vehicle}/{e.Code}({e.FaultCount})");
Console.WriteLine($"faulty: {string.Join(", ", faulty)}");
// Query syntax (same thing, compiled to the same method calls)
var lowVoltage = from e in ecus
where e.Voltage < 12.5
orderby e.Voltage
select new { e.Code, e.Voltage };
Console.WriteLine($"low voltage: {string.Join(", ", lowVoltage.Select(x => $"{x.Code}={x.Voltage}"))}");
// Grouping
foreach (var g in ecus.GroupBy(e => e.Vehicle).OrderBy(g => g.Key))
Console.WriteLine($" {g.Key}: {g.Count()} ECUs, {g.Sum(e => e.FaultCount)}
…Expected Result
The deferred query printed <1><2><3><4> only when string.Join enumerated it — and it included 4, which was added after the query was created. The ToList() snapshot did not include 5.
faulty: V3/EPS(5), V1/BMS(3), V2/VCU(2), V1/ABS(1)
low voltage: EPS=11.9, VCU=12.1, BMS=12.4
V1: 3 ECUs, 4 faults, avg 12.57 V
V2: 2 ECUs, 2 faults, avg 12.30 V
V3: 1 ECUs, 5 faults, avg 11.90 V
join: Aurora:VCU Aurora:BMS Aurora:ABS Hauler:VCU Hauler:BMS
group join: Aurora=3 Hauler=2 Spark=0
zip: a1 b2 c3
SelectMany: P0A80 U0100 C1234
Count 6, LongCount 6, Sum 108, Average 18, Min 4, Max 42, Aggregate(*) 7418880
MaxBy voltage: ABS, MinBy: EPS
First 4, First(>10) 15, FirstOrDefault(>100) 0, Last 42, ElementAt(2) 15
Single(==15) 15, SingleOrDefault(==99) 0, LastOrDefault(<0) 0
Single(>10): Sequence contains more than one matching element
Distinct 1,2,3 | Union 1,2,3,4 | Intersect 3 | Except 1,2
Any(>40) True, All(>0) True, Contains(23) True
ToList 6, ToArray [4,8,16,42], ToDictionary BMS=12.4, ToHashSet 4 codes
query created (nothing ran yet)
<1><2><3><4> -> 10,20,30,40
immediate snapshot: 10,20,30,40Common Mistakes
Enumerating a deferred query multiple times (re-running expensive work) — materialize with ToList() once.
Count() > 0 instead of Any().
First() on possibly empty data (throws) — use FirstOrDefault() and check.
Single() when more than one match is possible (throws "Sequence contains more than one matching element", as shown).
Heavy side effects inside Select (they run at an unexpected time).
OrderBy(...).OrderBy(...) instead of ThenBy (the second sort replaces the first).
Best Practices
Use LINQ for clarity; switch to loops only in measured hot paths.
Materialize (ToList) when you will enumerate more than once or when the source may change.
Keep lambdas pure; name intermediate queries.
With EF Core, keep queries translatable to SQL (Module 50).
Interview Relevance
Very high: "what is deferred execution?", "First vs FirstOrDefault vs Single", "Select vs SelectMany", "IEnumerable vs IQueryable", LINQ coding questions (group, join, top-N per group).
Practice Questions
Find the three ECUs with the most faults.
Group DTC codes by their first letter and count them.
Left-join vehicles with ECUs and list vehicles with no ECUs.
Compute the average voltage per vehicle, only for vehicles with more than one ECU.
Rewrite a nested foreach that builds a list of pairs using SelectMany.
Quick Quiz
Q1. (True/False) ecus.Where(...) runs the filter immediately.
Answer: False. Where is deferred (verified by the <1><2>... output).
Q2. (Predict the output) new[] { 1, 2, 3, 3 }.Distinct().Count() Answer: 3.
Q3. (Multiple choice) Which operator flattens a sequence of sequences? A) Select B) SelectMany C) Zip D) Concat Answer: B.
Q4. (Predict the output) new[] {4, 8, 15, 16, 23, 42}.Single(n => n > 10)
Answer: It throws InvalidOperationException: Sequence contains more than one matching element (verified).
Coding Challenge
Given test results (string Ecu, string TestId, bool Passed, double Seconds), print per ECU the pass rate and the slowest failing test, ordered by pass rate ascending.
var results = new[]
{
(Ecu: "VCU", TestId: "T1", Passed: true, Seconds: 1.2),
(Ecu: "VCU", TestId: "T2", Passed: false, Seconds: 4.8),
(Ecu: "BMS", TestId: "T3", Passed: true, Seconds: 2.0),
(Ecu: "BMS", TestId: "T4", Passed: true, Seconds: 0.7),
(Ecu: "ABS", TestId: "T5", Passed: false, Seconds: 3.3),
};
var report = results.GroupBy(r => r.Ecu)
.Select(g => new
{
Ecu = g.Key,
PassRate = g.Count(r => r.Passed) / (double)g.Count(),
// MaxBy would throw on an empty sequence of value tuples, so order and take the first (or null)
SlowestFail = g.Where(r => !r.Passed).OrderByDescending(r => r.Seconds).Select(r => r.TestId).FirstOrDefault()
})
.OrderBy(x => x.PassRate);
foreach (var r in report)
Console.WriteLine($"{r.Ecu}: {r.PassRate:P0} passed, slowest failure: {r.SlowestFail ?? "-"}");