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#.