Skip to content
Search lessons, topics, tests…
Esc

    ↑ ↓ moveEnter openEsc close
    Cheat sheets: all cheat sheets

    Generic Types and Generic Methods in C#

    Choose between generic types and generic methods.

    Start with the calls

    Why is T fixed for a Box<int> instance but inferred separately at each Swap or Max call? Start with the existing calls, then compare where each type parameter is declared.

    Executable code examples

    Generic type and generic method example

    Program.cs

    C#Runs
    using System;
    
    public sealed class Box<T>
    {
        public T Value { get; }
    
        public Box(T value)
        {
            Value = value;
        }
    }
    
    public static class Algorithms
    {
        public static void Swap<T>(ref T left, ref T right)
        {
            T temp = left;
            left = right;
            right = temp;
        }
    
        public static T Max<T>(T first, T second) where T : IComparable<T>
        {
            return first.CompareTo(second) >= 0 ? first : second;
        }
    }
    
    public static class Program
    {
        public static void Main()
        {
            var numberBox = new Box<int>(42);
            var textBox = new Box<string>("hello");
    
            int a = 10;
            int b = 20;
            Algorithms.Swap(ref a, ref b);
    
            string left = "pear";
            string right = "apple";
            Algorithms.Swap(ref left, ref right);
    
            int largerNumber = Algorithms.Max(12, 7);
            string laterWord = Algorithms.Max("ant", "zebra");
    
            Console.WriteLine($"Box<int>: {numberBox.Value}");
            Console.WriteLine($"Box<string>: {textBox.Value}");
            Console.WriteLine($"Swapped ints: {a}, {b}");
            Console.WriteLine($"Swapped strings: {left}, {right}");
            Console.WriteLine($"Max int: {largerNumber}");
            Console.WriteLine($"Max string: {laterWord}");
        }
    }

    Deep dive

    A generic method declares its own type parameter list after the method name. The compiler can often infer the type argument from the method call, so you do not always need to write angle brackets explicitly. For example, when a method accepts T value, calling it with 42 lets the compiler infer T as int.

    A generic type declares its type parameters after the type name. Every member of that type can then use the class-level type argument. This is why List<T> can expose Add(T item) and T this[int index] without repeating the element type everywhere.

    Constraints narrow what a type parameter is allowed to be. Without an appropriate constraint, you cannot assume that T has a particular domain member, ordering operation, or constructor. If a method needs comparison, it can require where T : IComparable<T>. That lets the method call CompareTo while still staying generic.

    A useful mental model is:

    • Use a generic type when the variation belongs to the whole abstraction, such as a collection, cache, repository, or option wrapper.
    • Use a generic method when only a single operation needs to vary by type.

    A generic method can also live inside a generic type. When that happens, keep the type parameter names distinct. Reusing the same identifier such as T for both the type and the method is confusing and can trigger warnings about hiding. Prefer clearer names like TItem, TValue, or U.

    The complete program in Executable code examples above shows both shapes together: Box<T> puts the type parameter on the type, while Swap<T> and Max<T> put it on individual methods. Compare their declarations and the corresponding calls.

    Failure modes

    The most common mistake is treating generics like a way to avoid thinking about types. Generics are not “any type, do whatever you want”; they are “any type that satisfies the contract.” If your generic algorithm needs an interface member or construction through new T(), state the needed contract with a suitable constraint. For ordering or equality, you can also accept a comparer; not every algorithm needs to constrain the element type.

    Another common mistake is assuming the compiler can infer a type argument from a return value alone. In practice, inference is driven by the supplied arguments. The compiler does not infer a method type argument solely from its return type or constraints. For a method whose type parameter appears in no input from which it can be inferred, supply the type argument explicitly.

    Also avoid using object as a fake generic mechanism. It loses compile-time safety and often causes boxing or cast failures later. Likewise, avoid inventing separate SwapInt, SwapString, and SwapGuid overloads when one generic method would do the job better.

    Interview drill

    Be ready to answer these questions:

    1. What is the difference between List<T> and Swap<T>?
    2. Why is List<object> not a substitute for List<int>?
    3. When can the compiler infer T automatically, and when can it not?
    4. Why do constraints exist, and what do they enable?
    5. What problem does generics solve beyond code reuse?

    Answer key

    1. Scope of the type parameter: List<T> is a generic type, so choosing List<int> fixes the element type for that constructed list type. Swap<T> is a generic method, so separate calls can use different type arguments. A non-generic method inside a generic type can use the enclosing type parameter without declaring its own.
    2. Different contracts: List<object> permits mixed values and returns elements as object; an inserted int is boxed. List<int> accepts integers and returns them as int, avoiding that boxing and the retrieval cast. Neither list type is a substitute for the other.
    3. Inference needs call arguments: Swap(ref a, ref b) infers int when both variables are integers. A return type or constraint alone is insufficient; specify the type argument when the inputs do not determine it.
    4. Constraints make operations legal: where T : IComparable<T> lets the compiler verify the call to CompareTo. The constraint checks a type capability, not the validity of every value passed at runtime.
    5. Type-safe API relationships: Generics connect input, stored, and returned types, so callers keep static checking and avoid object-based casts. Avoiding boxing in cases such as List<int> can also reduce overhead, but generics are not a blanket performance guarantee.

    A concise interview answer: generics express reusable algorithms and data structures while preserving the relationships between their types.

    Revision checklist

    • I can define a generic type and a generic method.
    • I can explain type inference for generic methods.
    • I can explain why constraints are needed.
    • I can choose between a deliberately heterogeneous object API and a generic API that preserves a type relationship.
    • I can identify when to place the type parameter on the type versus on the method.
    • I can describe why generics improve both safety and reuse.

    Production code

    The following console example demonstrates both a generic type and generic methods. It uses a generic Box<T> type, a generic Swap<T> method, and a constrained Max<T> method. The calls illustrate type-safe storage, swapping, and comparison for the supplied values. This is a teaching example, not a fully defensive comparison API.

    Code walkthrough

    Box<T> is a generic type. The T placeholder is fixed when the caller chooses Box<int> or Box<string>. That means the compiler knows exactly what Value contains in each case.

    Swap<T> is a generic method. The algorithm is written once, but it works for any type that can be assigned and moved through a temporary variable. The compiler infers T from the ref arguments, so calls such as Algorithms.Swap(ref a, ref b) stay concise.

    Max<T> shows a constraint. Without where T : IComparable<T>, the method would not be allowed to call CompareTo. The constraint lets the method remain reusable while still supporting a meaningful operation. Max delegates ordering to T.CompareTo; for string, this uses current-culture, case-sensitive ordering, not ordinal ordering. An API needing ordinal or caller-selected ordering should make its comparer policy explicit.

    The constraint is not a runtime null check. This simple implementation assumes first is non-null when T is a reference type; otherwise calling first.CompareTo(second) throws. Production code should define its null policy.

    Notice how the example never casts. That is the real benefit of generics: the API stays flexible, but the compiler still protects you from mixing incompatible types.

    Sources

    TechInterviewNotes Editorial Team · Updated 28 Oct 2026 ·