Lambda Expressions
Lambdas are inline anonymous functions — (x, y) => x + y — powering LINQ, Task.Run, and fluent configuration.
Introduction
Lambdas are inline anonymous functions — (x, y) => x + y — powering LINQ, Task.Run, and fluent configuration. Expression lambdas compile to delegates; expression trees (Expression<Func>) enable EF Core SQL translation.
Closures capture outer variables by reference — interviewers ask closure loop bug in foreach before C# 5 and allocation implications.
The story
A product catalog API filters premium items — price at or above $1,000 — within a specific category for a "Luxury" storefront page. A static lambda defines the premium predicate once without capturing local variables, avoiding extra heap allocations on every request in a high-traffic e-commerce service.
Understanding the topic
Key concepts
- Syntax: (params) => expression or { statements }
- Implicit typing on parameters when delegate known.
- Closure captures local variables lifetime extended.
- Expression
> vs Func — tree vs delegate. - Static lambda (C# 9) avoids capture allocation.
- Discard _ for unused parameters.
Step-by-step explanation
- Compiler generates closure class for captures.
- Assign lambda to Action/Func matching signature.
- Pass to LINQ Where, Select methods.
- Expression trees analyzed by EF provider.
- Local functions alternative when reuse in method.
- ref readonly params in advanced scenarios.
Practical code example
LINQ filtering with lambda and static lambda avoiding capture:
namespace TechLearningPro.Lambdas;public record Product(string Category, decimal Price);public static class ProductFilters{private static readonly Func<Product, bool> IsPremium =static p => p.Price >= 1000m; // static lambda — no closurepublic static IEnumerable<Product> ExpensiveInCategory(IEnumerable<Product> products,string category) =>products.Where(p => p.Category == category && IsPremium(p));}
Line-by-line code explanation
record Product(string Category, decimal Price)is the entity being filtered.Func<Product, bool> IsPremium = static p => p.Price >= 1000mstores a reusable predicate delegate.static p =>marks a static lambda that cannot capture surrounding locals — zero closure allocation.p.Price >= 1000mdefines the premium threshold inline.ExpensiveInCategory(..., string category)combines category and premium filters.products.Where(p => p.Category == category && IsPremium(p))chains LINQ with the named predicate.p.Category == categorycaptures the method parameter in the lambda — allowed here because it is a parameter, not a local.IsPremium(p)reuses the static predicate instead of duplicating the price check.
Key takeaway: static lambda cannot capture locals — zero allocation closure. Combine with local function for complex multi-line filters.
Real-world use
Where you'll use this in production
- LINQ queries in repositories.
- FluentValidation rule definitions.
- Task continuations ContinueWith (prefer await).
- ASP.NET route filters inline predicates.
Best practices
- Use static lambda when no capture needed.
- Extract complex lambdas to named local functions.
- Avoid modifying captured variables confusingly.
- Expression trees only when provider needs them.
- Keep lambdas short for readability.
Common mistakes
- Closure loop variable bug (historical — foreach fixed).
- Accidental capture of large object graph.
- Statement lambda where expression sufficient.
- Using Func where Expression needed for EF.
Advanced interview questions
Interview Prep
Practice concise answers, then expand each card for the explanation.
1BeginnerQuestionLambda syntax?+
Answer
2BeginnerQuestionClosure?+
Answer
3IntermediateQuestionExpression vs delegate lambda?+
Answer
4IntermediateQuestionstatic lambda benefit?+
Answer
5AdvancedQuestionEF Core filter by dynamic user input safely.+
Answer
Summary
Lambdas provide concise inline delegates. Closures capture context — mind allocations. Expression trees enable LINQ-to-SQL translation. static lambda optimizes hot paths. Next: generics for type-safe reusable code.