Generic Algorithms

Vaibhav • September 11, 2025

In the previous article, we explored how reflection allows us to inspect and manipulate generic types at runtime. Now we shift our focus to something more practical and performance-oriented: generic algorithms. These are algorithms written using generics so they can operate on a wide range of types without sacrificing type safety or performance. Whether you're sorting, filtering, searching, or transforming data, generic algorithms help you write reusable logic that adapts to the types you work with.

What Are Generic Algorithms?

A generic algorithm is a method or function that performs a computation or transformation using type parameters. Instead of hardcoding the type (like int or string), you use a generic type parameter (T) so the algorithm works for any type that satisfies the required constraints.

public static T Max<T>(List<T> items) where T : IComparable<T>
{
    if (items == null || items.Count == 0)
        throw new ArgumentException("List cannot be empty");

    T max = items[0];
    foreach (T item in items)
    {
        if (item.CompareTo(max) > 0)
            max = item;
    }
    return max;
}

This algorithm finds the maximum value in a list of any type T, as long as T implements IComparable<T>. It works for int, string, DateTime, or any custom type that supports comparison.

Why Use Generic Algorithms?

Generic algorithms help you:

- Avoid code duplication across types.

- Maintain type safety and avoid casting.

- Improve performance by avoiding boxing/unboxing.

- Write cleaner, more expressive code.

They’re especially useful in libraries, utilities, and frameworks where the logic is independent of the specific type.

Example: Generic Swap Algorithm

Swapping two values is a common operation. Here’s a generic version:

public static void Swap<T>(ref T a, ref T b)
{
    T temp = a;
    a = b;
    b = temp;
}

This method works for any type-int, string, Point, etc. It uses ref parameters to modify the original variables. The logic is simple, but the generic version makes it reusable across your entire codebase.

Example: Generic Filter Algorithm

Filtering a list based on a condition is another common task. Here’s a generic filter method:

public static List<T> Filter<T>(List<T> items, Func<T, bool> predicate)
{
    List<T> result = new List<T>();
    foreach (T item in items)
    {
        if (predicate(item))
            result.Add(item);
    }
    return result;
}

This method takes a list of items and a predicate function, and returns a new list containing only the items that match the condition. It works for any type and any condition.

var numbers = new List<int> { 1, 2, 3, 4, 5 };
var evens = Filter(numbers, x => x % 2 == 0);

This example filters out even numbers. You can use the same method to filter strings, dates, or custom objects.

Example: Generic Transform Algorithm

Transforming a list from one type to another is a common pattern. Here’s a generic map method:

public static List<TResult> Map<TSource, TResult>(List<TSource> items, Func<TSource, TResult> selector)
{
    List<TResult> result = new List<TResult>();
    foreach (TSource item in items)
    {
        result.Add(selector(item));
    }
    return result;
}

This method takes a list of source items and a selector function, and returns a new list of transformed results. It’s the foundation of LINQ’s Select method.

var names = new List<string> { "Alice", "Bob" };
var lengths = Map(names, name => name.Length);

This example maps names to their lengths. You can use the same method to convert objects, extract fields, or compute derived values.

Example: Generic Search Algorithm

Searching for an item in a list is another common task. Here’s a generic search method:

public static T FindFirst<T>(List<T> items, Func<T, bool> predicate)
{
    foreach (T item in items)
    {
        if (predicate(item))
            return item;
    }
    return default(T);
}

This method returns the first item that matches the condition, or default(T) if none is found. It works for any type and any condition.

var words = new List<string> { "cat", "dog", "elephant" };
var longWord = FindFirst(words, w => w.Length > 5);

This example finds the first word longer than five characters. You can use the same method to search numbers, dates, or custom objects.

Example: Generic Sort Algorithm

Sorting a list requires comparison. Here’s a generic sort method using IComparer<T>:

public static void Sort<T>(List<T> items, IComparer<T> comparer)
{
    items.Sort(comparer);
}

This method delegates to the built-in Sort method but allows you to pass a custom comparer. You can sort by name, date, score, or any other criteria.

var people = new List<Person> { ... };
Sort(people, new PersonNameComparer());

This example sorts people by name using a custom comparer. You can define your own IComparer<T> for any type.

Design Tips for Generic Algorithms

When writing generic algorithms:

- Use constraints to enforce required capabilities (like IComparable<T>).

- Use Func<T, TResult> and Predicate<T> for flexibility.

- Avoid assumptions about the type-let the caller provide logic.

- Return default(T) when no result is found.

- Keep the algorithm focused and composable.

Write generic algorithms that do one thing well. Keep them small, composable, and testable. Use delegates to inject behavior and constraints to enforce correctness.

Testing Generic Algorithms

Generic algorithms should be tested with multiple types:

- Value types like int, double, bool

- Reference types like string, List<T>, custom classes

- Nullable types like int?, DateTime?

- Edge cases like empty lists, null values, and duplicates

This ensures your algorithm behaves correctly across scenarios and doesn’t rely on type-specific behavior.

Summary

Generic algorithms are reusable, type-safe methods that perform computations across a wide range of types. You’ve learned how to write generic algorithms for swapping, filtering, mapping, searching, and sorting. You’ve seen how to use constraints, delegates, and default(T) to make your algorithms safe and flexible. You’ve also learned how to test and design them for clarity and performance. In the next article, we’ll wrap up Chapter 13 with a review and practical exercises to reinforce your understanding of generics and type safety.