← Open in the full interactive course (progress tracking, search & more)

A named shortcut for "does this thing match a condition?" — functionally the same as Func<T, bool>, with a history worth knowing.

You just learned that Func<T, bool> means "takes a T, returns true or false." That exact shape — asking a yes/no question about a single value — comes up so often that .NET gave it its own dedicated name, years before Func even existed: Predicate<T>.

You'll meet Predicate<T> in specific corners of the BCL, most notably on List<T>. Understanding it — and understanding why modern code mostly reaches for Func<T, bool> instead — will save you a moment of confusion the first time you see both used to mean the same thing.

What Is It?

The Simple Explanation

A predicate, in logic and computer science generally, is a function that returns true or false for a given input — a yes/no test. Predicate<T> in C# is a built-in delegate type that represents exactly this: "does this T satisfy some condition?"

The Technical Definition

public delegate bool Predicate<T>(T obj);

Compare that to the shape you already know:

public delegate TResult Func<T, TResult>(T arg);
// with TResult fixed to bool:
// public delegate bool Func<T, bool>(T arg);

They're structurally identical: one parameter of type T, one bool return value. Predicate<T> isn't defined in terms of Func<T, bool> — they're two separately-declared delegate types that just happen to describe the same signature.

Why Does It Exist?

The Problem — Timing

Predicate<T> was introduced in .NET Framework 2.0, in 2005 — the same release that introduced generics to C# at all. Func and Action didn't arrive until .NET 3.5, in 2007, alongside LINQ. When the List<T> class was designed and needed methods like "find the first item matching a condition," Func<T, bool> simply didn't exist yet — Predicate<T> was the tool available at the time, so that's what List<T> was built with.

The Solution — and Why It Stuck Around

Once List<T>.Find, FindAll, RemoveAll, Exists, and similar methods shipped taking Predicate<T> parameters, changing their signatures later would have been a breaking change for every piece of code calling them. So Predicate<T> stayed — it's now a permanent, unremovable part of those specific APIs, even though Func<T, bool> would be the more "modern" choice if those methods were designed today.

The key insight

Predicate<T> isn't a mistake or a redundancy to avoid — it's a snapshot of an earlier design era, preserved for compatibility. Once you know that, seeing both Predicate<T> and Func<T, bool> in the same codebase stops being confusing.

Big Picture

Predicate<T> — Legacy-Era API Shape

Func<T, bool> — Modern Idiom

How It Works

USING Predicate<T> WITH List<T>
1. WRITE A CONDITION — A METHOD OR A LAMBDA
bool IsExpensive(decimal price) => price > 100m;
2. PASS IT TO A List<T> METHOD EXPECTING Predicate<T>
List<decimal> prices = new() { 40m, 150m, 99m, 250m };
decimal? firstExpensive = prices.Find(IsExpensive);
3. List<T> RUNS THE PREDICATE INTERNALLY

Simple Example

using System;
using System.Collections.Generic;

class Program
{
    static bool IsEven(int n) => n % 2 == 0;

    static void Main()
    {
        List numbers = new() { 3, 8, 15, 22, 41, 60 };

        // Predicate, explicitly
        Predicate isEven = IsEven;
        Console.WriteLine(isEven(8)); // True

        // List methods that take Predicate
        int firstEven = numbers.Find(isEven);           // 8
        List allEven = numbers.FindAll(isEven);     // [8, 22, 60]
        int removedCount = numbers.RemoveAll(isEven);    // removes 8, 22, 60
        bool anyOdd = numbers.Exists(n => n % 2 != 0);   // lambda works too — True

        Console.WriteLine($"First even: {firstEven}");
        Console.WriteLine($"All even: {string.Join(", ", allEven)}");
        Console.WriteLine($"Removed: {removedCount}");
        Console.WriteLine($"Any odd remaining: {anyOdd}");
    }
}

Code → Meaning → Result: Find, FindAll, and RemoveAll all accept a Predicate<int> — the same isEven value (or an inline lambda, as in Exists) works everywhere, because they're all asking the same kind of question: "does this item match?"

Real-World Example

Cleaning up a shopping cart before checkout — removing any line item that's out of stock — is a natural fit for List<T>.RemoveAll:

using System;
using System.Collections.Generic;

public record CartItem(string ProductName, int QuantityInStock);

class Program
{
    static void Main()
    {
        var cart = new List
        {
            new("Wireless Mouse", QuantityInStock: 12),
            new("USB-C Cable", QuantityInStock: 0),
            new("Mechanical Keyboard", QuantityInStock: 3),
            new("Webcam", QuantityInStock: 0),
        };

        int removed = cart.RemoveAll(item => item.QuantityInStock == 0);

        Console.WriteLine($"Removed {removed} out-of-stock item(s).");
        foreach (var item in cart)
            Console.WriteLine($"- {item.ProductName} ({item.QuantityInStock} in stock)");
        // Removed 2 out-of-stock item(s).
        // - Wireless Mouse (12 in stock)
        // - Mechanical Keyboard (3 in stock)
    }
}

Notice the lambda item => item.QuantityInStock == 0 is passed directly — you rarely declare a named Predicate<CartItem> variable in practice; you write the check inline. Lambdas are covered fully in the next lesson.

Analogy

Two Different Bouncers, Same Job

Predicate<T> and Func<T, bool> are like two bouncers hired by different clubs, using the exact same rulebook — "let this person in if they meet the condition, otherwise don't." They ask the identical question and give the identical kind of answer. The only difference is which club (which API) hired which bouncer: older venues (List<T>'s legacy methods) hired Predicate<T> back when it was the only bouncer in town; newer venues (LINQ) hired Func<T, bool> instead.

Under the Hood

Predicate<T> and Func<T, bool> are two genuinely distinct types at the CLR level — despite matching signatures, one is not implicitly convertible to a variable of the other's type. What is implicitly convertible to either is a method group or a lambda expression, because the compiler performs that conversion based on shape-matching at the point of assignment, not by relating the two delegate types to each other. This is exactly why numbers.Find(isEven) works with a plain method reference, and why an inline lambda works for any method expecting either delegate type — the compiler builds a fresh delegate instance of whichever type is actually required at that call site.

Common Confusion

1. "I can pass a Predicate<T> variable where a Func<T, bool> is expected" — you can't, directly

Even though the signatures match, a variable already typed as Predicate<int> cannot be assigned directly to a Func<int, bool> variable — they are unrelated types, and there's no implicit conversion between two already-constructed delegate instances. What does work is assigning the same underlying method or lambda to variables of either type separately.

2. "Predicate is deprecated / obsolete" — not officially, just legacy-scoped

Predicate<T> is not marked obsolete and isn't going away — it's simply confined, in practice, to the specific older APIs that were built around it before Func existed. It's fine to use where the API requires it; you just don't need to introduce it in your own new method signatures.

Common Mistakes

Mistake 1 — Declaring new public APIs with Predicate<T> today

Writing public List<T> Filter(Predicate<T> condition) in new code, purely out of habit from seeing List<T>.Find.

Use Func<T, bool> for your own methods — it's the idiom the rest of the modern ecosystem (especially LINQ) expects and composes with.

Mistake 2 — Trying to directly assign between the two delegate types

Func<int, bool> f = somePredicateVariable; — this does not compile, because the two are unrelated named delegate types.

Wrap it: Func<int, bool> f = x => somePredicateVariable(x);, or better, just re-declare the underlying logic as a lambda and assign it to whichever delegate type you actually need.

When Should I Use It?

Rule of thumb: If you're consuming an existing API, use whatever delegate type it asks for (often via a lambda, so you won't even think about it). If you're designing a new API, Func<T, bool> is the modern default.

Mental Model

Predicate<T> = "does this T pass the test?" — same shape as Func<T, bool>.
Different name, same job, different era.

See it → List<T>'s classic methods.
Write it → almost never; prefer Func<T, bool>.

Key Takeaway


Check Your Understanding

Let's confirm you can place Predicate<T> correctly alongside Func<T, bool>.

1. What is the relationship between Predicate<T> and Func<T, bool>?

Show answer

Correct: B

Why B is correct: Both delegate types describe "take a T, return a bool," but they were declared independently, at different points in .NET's history, and remain distinct types today.

Why A is incorrect: Predicate<T> predates Func entirely and is its own delegate declaration, not an alias.

Why C is incorrect: Delegate types don't inherit from each other this way; both derive from System.MulticastDelegate independently.

Why D is incorrect: Both take exactly one parameter of type T and return bool — the signatures match precisely.

Reinforcement: Matching signature does not imply type compatibility for named delegate types in C#.

2. Why does List<T>.Find still use Predicate<T> instead of Func<T, bool>?

Show answer

Correct: B

Why B is correct: Predicate<T> shipped with generics in .NET 2.0, well before Func arrived with LINQ in .NET 3.5. Once List<T>'s public API was locked in using Predicate<T>, changing it would be a breaking change for existing callers.

Why A is incorrect: Nothing about Func<T, bool> is incompatible with List<T> — it's purely a historical API design choice, not a technical limitation.

Why C is incorrect: There is no meaningful performance difference between the two — both are ordinary delegate invocations.

Why D is incorrect: Predicate<T> is not deprecated or scheduled for removal; it remains a permanent part of these APIs.

Reinforcement: API compatibility, not technical necessity, is why older delegate types persist in the BCL.

3. You're designing a brand-new public method today that needs a caller-supplied "does this item qualify?" check. Which is the better choice, and why?

Show answer

Correct: B

Why B is correct: New code benefits from consistency with LINQ and the rest of the modern BCL, which is built almost entirely on Func/Action. Callers of your API also get to reuse the same lambdas they'd already write for LINQ queries.

Why A is incorrect: Both are equally short at the call site; length isn't the deciding factor, and using Predicate<T> creates unnecessary inconsistency with the rest of your codebase.

Why C is incorrect: While both work technically, using Func<T, bool> is the established convention for new C# APIs — consistency matters for readability and interoperability with LINQ-style code.

Why D is incorrect: A one-off custom delegate for a shape this common only adds an unnecessary type when a standard BCL delegate already fits perfectly.

Reinforcement: For new designs, default to Func<T, bool> over Predicate<T> unless you're specifically matching an existing legacy API.

You can now recognize Predicate<T> for what it is and know when to reach for Func<T, bool> instead. Next: the => syntax you've already been reading in every example — lambda expressions, formally.


dotnetmadeeasy.com — Learn C# and .NET, the right way.