Functional Programming Exercises
Vaibhav • September 11, 2025
In the last several articles, we’ve explored the core concepts of functional programming in C#: delegates, lambda expressions, closures, higher-order functions, and predicate delegates. These tools allow us to treat behavior as data, write modular and expressive code, and build logic pipelines that are both powerful and readable. Now it’s time to put these ideas into practice.
This article presents a series of hands-on exercises designed to reinforce your understanding of functional programming in C#. Each exercise builds on concepts introduced in Chapter 14 and earlier chapters, helping you apply what you’ve learned in real-world scenarios. We’ll walk through each problem, explain the solution step-by-step, and highlight the functional techniques used.
Exercise: Filtering with Predicate Delegates
Let’s start with a simple task: filter a list of integers to keep only the even numbers. You’ll use a Predicate<int>
delegate and the List<T>.FindAll
method.
List<int> numbers = new List<int> { 1, 2, 3, 4, 5, 6 };
Predicate<int> isEven = n => n % 2 == 0;
List<int> evens = numbers.FindAll(isEven);
Console.WriteLine(string.Join(", ", evens)); // Output: 2, 4, 6
The predicate isEven
returns true
for even
numbers. FindAll
uses this to build a new list containing only the matching
elements. This exercise reinforces how predicates encapsulate decision logic.
Exercise: Creating a Closure-Based Counter
Write a method that returns a function which increments and returns a counter value each time it’s called. This demonstrates closures and captured variables.
Func<int> CreateCounter()
{
int count = 0;
return () =>
{
count++;
return count;
};
}
var counter = CreateCounter();
Console.WriteLine(counter()); // Output: 1
Console.WriteLine(counter()); // Output: 2
The lambda captures the count
variable and retains access to it across calls.
This is a classic closure - the function “remembers” its environment.
Exercise: Building a Dynamic Filter Generator
Write a method that returns a predicate based on a threshold. This shows how higher-order functions can generate behavior dynamically.
Predicate<int> GreaterThan(int threshold)
{
return x => x > threshold;
}
var isGreaterThanFive = GreaterThan(5);
Console.WriteLine(isGreaterThanFive(7)); // Output: True
Console.WriteLine(isGreaterThanFive(3)); // Output: False
The returned predicate captures the threshold
variable. This closure allows you
to create customized filters on the fly.
Exercise: Composing Functions
Write two functions and compose them into a single function that applies both transformations. This demonstrates manual function composition.
Func<int, int> doubleIt = x => x * 2;
Func<int, int> addTen = x => x + 10;
Func<int, int> composed = x => addTen(doubleIt(x));
Console.WriteLine(composed(3)); // Output: 16
The composed function applies doubleIt
first, then addTen
. This pattern is useful for building logic pipelines.
Exercise: Using Action for Logging
Write a method that accepts an Action<string>
delegate and uses it to log
messages. This shows how behavior can be injected into a method.
void ProcessData(List<int> data, Action<string> logger)
{
foreach (var item in data)
{
logger($"Processing item: {item}");
}
}
ProcessData(new List<int> { 1, 2, 3 }, Console.WriteLine);
The logger
delegate is passed in and used to print messages. You can swap it
out for different logging strategies - such as writing to a file or sending to a server.
Exercise: Event-Driven Observer with Predicate Filtering
Combine events and predicates to build a reactive system. Create a sensor that raises an event, and an observer that filters notifications using a predicate.
public class SensorEventArgs : EventArgs
{
public int Value { get; }
public SensorEventArgs(int value)
{
Value = value;
}
}
public class Sensor
{
public event EventHandler<SensorEventArgs> ValueChanged;
public void Emit(int value)
{
ValueChanged?.Invoke(this, new SensorEventArgs(value));
}
}
public class FilteredObserver
{
private readonly Predicate<int> _filter;
public FilteredObserver(Predicate<int> filter)
{
_filter = filter;
}
public void Subscribe(Sensor sensor)
{
sensor.ValueChanged += OnValueChanged;
}
private void OnValueChanged(object sender, SensorEventArgs e)
{
if (_filter(e.Value))
{
Console.WriteLine($"Accepted value: {e.Value}");
}
}
}
var sensor = new Sensor();
var observer = new FilteredObserver(x => x % 2 == 0);
observer.Subscribe(sensor);
sensor.Emit(3); // No output
sensor.Emit(4); // Output: Accepted value: 4
The observer uses a predicate to filter incoming values. Only values that pass the test are printed. This pattern is useful for building reactive systems with custom logic.
Exercise: Aggregating with Func
Use Func<T, T, T>
to aggregate a list of values. This shows how
functional delegates can be used for reduction.
List<int> numbers = new List<int> { 1, 2, 3, 4 };
Func<int, int, int> sum = (a, b) => a + b;
int total = numbers.Aggregate(sum);
Console.WriteLine(total); // Output: 10
The Aggregate
method applies the sum
function
to combine all elements. This is a functional way to reduce a collection to a single value.
Exercise: Generating a List of Functions
Create a list of functions that each multiply by a different factor. This demonstrates closures and dynamic function generation.
List<Func<int, int>> multipliers = new List<Func<int, int>>();
for (int i = 1; i <= 3; i++)
{
int factor = i;
multipliers.Add(x => x * factor);
}
foreach (var f in multipliers)
{
Console.WriteLine(f(5));
}
// Output:
// 5
// 10
// 15
Each lambda captures a different value of factor
. This shows how closures can
be used to generate customized behavior in loops.
Summary
These exercises demonstrate the power and flexibility of functional programming in C#. You practiced using delegates, lambdas, closures, higher-order functions, and predicates to write expressive, modular code. You learned how to filter data, manage state, compose behavior, and build reactive systems - all using functional techniques.
Functional programming is not just a theoretical concept - it’s a practical toolset that helps you write cleaner, more maintainable code. By mastering these patterns, you’ll be better equipped to tackle real-world problems and build scalable applications.
With this final article, you’ve completed Chapter 14: Delegates, Events, and Functional Programming. In the next chapter, we’ll dive into LINQ and Query Expressions - where functional programming meets data querying, and you’ll learn how to write powerful, declarative queries in C#.