Delegates Interview Prep

C# Delegates Interview Questions and Answers (2026)

Delegates are one of those C# topics that's easy to use casually but surprisingly deep once an interviewer starts asking "but what actually happens when...". These are the delegate questions that come up most, from the basics of what a delegate is to the multicast return-value quirk that catches even experienced developers off guard.

  1. What is a delegate in C#?

    A delegate is a type-safe object that holds a reference to one or more methods with a matching signature, allowing methods to be passed as parameters, stored in variables, and invoked indirectly — effectively a type-safe, object-oriented function pointer. You declare a delegate type, then any method (static or instance) matching its signature can be assigned to it. Unlike raw C function pointers, delegates carry type information checked at compile time, can wrap instance methods (capturing the target object), and support multicasting (invoking multiple methods from one call). They're the foundation for events, LINQ's higher-order functions (Where(Func<T,bool>)), and callback-style APIs.

    public delegate int Operation(int a, int b);
    Operation add = (a, b) => a + b;
    Console.WriteLine(add(2, 3)); // 5
  2. What are `Action`, `Func`, and `Predicate`, and when do you use each?

    These are built-in generic delegate types that cover the vast majority of use cases so you rarely need to declare custom delegate types. Action<T1,...> represents a method that takes parameters and returns void (up to 16 parameters, or Action with none). Func<T1,...,TResult> represents a method that returns a value, with the last type parameter always being the return type. Predicate<T> is specifically Func<T, bool> — used historically in collection methods like List<T>.Find. In modern code, Func<T, bool> is generally preferred over Predicate<T> for consistency since most LINQ APIs use Func, and Predicate<T> is mostly legacy from pre-generic-heavy APIs.

    Func<int, int, int> multiply = (a, b) => a * b;
    Action<string> log = msg => Console.WriteLine(msg);
    Predicate<int> isEven = n => n % 2 == 0;
  3. What is a multicast delegate, and how does invocation work?

    Every delegate in C# is potentially multicast — it internally maintains an invocation list, and using += (or Delegate.Combine) appends another method to that list rather than replacing it. When invoked, all methods in the list run sequentially in the order they were added. If the delegate returns a value, only the last method's return value is actually returned to the caller — the others' return values are discarded, which is a common trap, especially with Func<> events where only one subscriber's answer "wins." If any method in the list throws, the remaining methods in the list are never invoked, and the exception propagates to the caller. -= removes a specific method from the list via Delegate.Remove.

    Action greet = () => Console.WriteLine("Hi");
    greet += () => Console.WriteLine("Bye");
    greet(); // prints both, in order
  4. When would you use a delegate instead of an interface?

    Both let you decouple "what to do" from "how it's implemented," but delegates suit single-method, stateless, often short-lived behavior — especially when a lambda or method group is more concise than defining an implementing class. Interfaces suit contracts with multiple related methods, when the implementer needs to hold state across calls, or when you need polymorphic behavior across a family of types with shared identity. A practical signal: if you find yourself defining an interface with exactly one method purely to pass a behavior around, a Func<>/Action<> delegate parameter is usually simpler and avoids boilerplate class definitions — this is exactly the pattern LINQ, Task.Run, and IEnumerable<T>.Where rely on. Interfaces remain preferable when the abstraction is a genuine "role" a type plays (e.g., IComparer<T>, IDisposable).

  5. What's the difference between events and plain delegates?

    An event is a delegate field wrapped with restricted access: outside the declaring class, code can only +=/-= subscribe or unsubscribe, not invoke the delegate directly or reassign it with = (which would wipe out other subscribers). A plain public delegate field has none of these restrictions — any external code could call it directly or overwrite the entire invocation list, breaking encapsulation. Events formalize the publisher/subscriber pattern: the publisher class exposes event EventHandler SomethingHappened; and only it can Invoke/raise the event internally, while any number of subscribers safely register handlers without risk of interfering with each other. This is why events, not raw delegates, are the idiomatic choice for notification APIs.

    public class Button
    {
        public event EventHandler? Clicked;
        public void SimulateClick() => Clicked?.Invoke(this, EventArgs.Empty);
    }
  6. What's the difference between anonymous methods and lambda expressions?

    Anonymous methods (delegate(int x) { return x * 2; }, C# 2.0) and lambda expressions (x => x * 2, C# 3.0+) both let you define inline, unnamed method bodies assignable to a delegate, and they compile to functionally equivalent IL — a lambda is essentially syntactic sugar with type inference and a more concise syntax. Lambdas additionally support expression-bodied syntax that can be compiled to Expression<TDelegate> trees (used by LINQ providers like Entity Framework to translate code into SQL), which anonymous methods cannot do. In practice, anonymous methods are rarely written in new code — lambdas are strictly more capable and concise — but they're occasionally seen when you need to omit parameters entirely with delegate { ... }, which lambdas can't express as tersely.

  7. What is variance in delegates (covariance/contravariance), and why does it matter?

    Delegate variance lets a method with a *more derived* return type or *less derived* parameter type be assigned to a delegate whose signature is technically different, as long as it's still safe. Covariance applies to return types: a delegate expecting Func<Animal> can be satisfied by a method returning Dog, since any caller expecting an Animal is fine getting a Dog. Contravariance applies to parameter types: a delegate expecting Action<Dog> can be satisfied by a method accepting Action<Animal>, since a method that can handle any Animal can certainly handle a Dog. This is enabled by the in/out generic parameter modifiers on generic delegate interfaces and mirrors the same variance rules used in generic interfaces like IEnumerable<out T>.

    Func<Dog> getDog = () => new Dog();
    Func<Animal> getAnimal = getDog; // covariant — valid
  8. What is a delegate's invocation list and how would you inspect it?

    Every delegate instance exposes its invocation list via GetInvocationList(), returning a Delegate[] of every method currently subscribed, in call order. This is useful for diagnostics (e.g., logging what's subscribed to an event before firing it), for manually invoking each target with individual exception handling instead of letting one failure abort the whole chain, or for implementing custom multicast semantics like collecting every subscriber's return value from a Func<> (since normal invocation only returns the last one). Delegates are immutable — +=/-= don't mutate the existing delegate object but instead create and assign a new delegate instance combining/removing targets, similar to how string concatenation creates a new string rather than mutating the original.

    foreach (Func<int> handler in multi.GetInvocationList())
    {
        try { results.Add(handler()); } catch (Exception ex) { Log(ex); }
    }
  9. Why are delegates described as "type-safe function pointers"?

    Like a C function pointer, a delegate stores an address to invoke later, decoupling the calling code from a specific hardcoded method. Unlike a raw function pointer, the delegate type itself encodes the exact method signature (parameter types and return type), so the compiler rejects any attempt to assign a mismatched method at compile time rather than causing undefined behavior or a crash at runtime. Delegates are also full .NET objects — reference types with identity, capable of capturing an instance target (for instance methods) alongside the method pointer, supporting multicasting, and participating safely in garbage collection. This combination of compile-time safety plus object-oriented capabilities is what "type-safe function pointer" specifically emphasizes versus C/C++ pointers.

  10. What are the performance considerations of using delegates?

    Delegate invocation has a small but real overhead compared to a direct method call, since it goes through an extra indirection layer; in tight, hot loops this can matter, though for most application code it's negligible next to I/O or allocation costs. More significant: every +=/-= and every lambda that captures outer variables allocates — combining delegates creates a new delegate instance, and closures allocate a compiler-generated class to hold captured variables, which adds GC pressure if done repeatedly in hot paths (e.g., inside a loop creating a new lambda each iteration). Multicast delegate invocation is also strictly sequential and synchronous — it doesn't parallelize subscriber calls — so a slow subscriber blocks all subsequent ones and the caller. For genuinely performance-critical paths, caching delegate instances instead of recreating them, and avoiding unnecessary closures, are the practical mitigations.

  11. What's the "Func<> return type quirk" to be aware of with multicast delegates?

    Because Func<>-typed multicast delegates only surface the *last* subscriber's return value when invoked normally, using events or delegates with a return type for anything beyond a single-subscriber contract is a footgun — earlier subscribers still run (side effects happen), but their return values are silently discarded with no compiler warning. This trips people up especially with event Func<...>-style "ask a question to whoever's listening" patterns, where developers assume they'll get an aggregate or first meaningful answer. The idiomatic fixes are: use GetInvocationList() to manually invoke each and collect results, restrict such delegates to single-subscriber use by convention, or redesign the API around a different pattern (e.g., a list of handlers each returning into a shared collection) instead of relying on a Func<> return value from a multicast call.

  12. What's the difference in accessibility rules between a delegate field/property and an event?

    A delegate declared as a plain field or auto-property (public Action OnDone;) exposes the full Delegate API to any caller with access — they can invoke it, reassign it with = (destroying existing subscribers), or clear it entirely. Declaring it as event (public event Action OnDone;) restricts external code to only +=/-=; invocation (OnDone?.Invoke()) and direct assignment are only permitted from within the declaring class (or a derived class if using field-like event declarations with appropriate access). You can also declare an event with custom add/remove accessors for advanced scenarios (e.g., delegating storage to a dictionary for a type with many rarely-used events), giving fine control over subscription behavior beyond the default compiler-generated backing delegate field. This access restriction is the entire reason event exists as distinct from a raw delegate member.

Practicing for a real interview? Get live AI help with your answers, in the browser, free to start.

Start a free session