Understanding Generics


Understanding Generics Interview with follow-up questions

1. What are Generics in .NET Framework?

Generics allow you to define classes, interfaces, methods, and delegates with type parameters — placeholders that are filled in by the caller at compile time. This enables writing a single reusable implementation that works correctly and type-safely with any data type.

Without generics (the problem):

// Non-generic: must cast, no type safety, boxing for value types
var list = new ArrayList();
list.Add(42);
list.Add("oops"); // No compile error!
int val = (int)list[0]; // Cast required; runtime error if wrong type

With generics (the solution):

// Generic: type-safe, no casting, no boxing
var list = new List();
list.Add(42);
// list.Add("oops"); // Compile error!
int val = list[0]; // No cast needed

Generic class:

public class Repository where T : class
{
    private readonly List _items = new();

    public void Add(T item) => _items.Add(item);
    public T? Find(Func predicate) => _items.FirstOrDefault(predicate);
    public IReadOnlyList GetAll() => _items.AsReadOnly();
}

// Usage
var repo = new Repository();
repo.Add(new Customer { Name = "Alice" });

Generic method:

public T Max(T a, T b) where T : IComparable
    => a.CompareTo(b) >= 0 ? a : b;

int m = Max(3, 7);       // T inferred as int
string s = Max("a", "b"); // T inferred as string

Benefits:

  • Type safety: Errors caught at compile time, not runtime.
  • Performance: Eliminates boxing/unboxing for value types — the runtime creates specialized versions for value types.
  • Code reuse: One implementation serves many types.
  • Intellisense and tooling: Full IDE support with correct types.
↑ Back to top

Follow-up 1

Why are Generics preferred over traditional data types?

Generics are preferred over traditional data types because they offer several advantages:

  1. Reusability: Generics allow you to write code that can work with multiple data types, reducing code duplication.

  2. Type Safety: Generics provide compile-time type checking, which helps catch type-related errors at compile time rather than at runtime.

  3. Performance: Generics can improve performance by avoiding the need for boxing and unboxing operations when working with value types.

  4. Code Clarity: Generics make the code more readable and maintainable by eliminating the need for casting or conversion operations.

Follow-up 2

Can you explain the concept of Generic methods and Generic classes?

Generic methods and generic classes are two ways to use generics in .NET Framework:

  1. Generic Methods: Generic methods allow you to define a method that can work with different data types. The type parameter is specified using angle brackets (<>) before the return type of the method. Example:
public T GetMax(T a, T b)
{
    return a.CompareTo(b) &gt; 0 ? a : b;
}
  1. Generic Classes: Generic classes allow you to define a class that can work with different data types. The type parameter is specified using angle brackets (<>) after the class name. Example:
public class Stack
{
    private List items = new List();

    public void Push(T item)
    {
        items.Add(item);
    }

    public T Pop()
    {
        T item = items[items.Count - 1];
        items.RemoveAt(items.Count - 1);
        return item;
    }
}

Follow-up 3

What is the role of constraints in Generics?

Constraints in generics allow you to restrict the types that can be used as type arguments in generic methods or generic classes. They provide a way to specify requirements for the type parameter. Some common constraints include:

  1. where T : class: Specifies that the type argument must be a reference type.

  2. where T : struct: Specifies that the type argument must be a value type.

  3. where T : new(): Specifies that the type argument must have a public parameterless constructor.

  4. where T : SomeBaseClass: Specifies that the type argument must be or derive from the specified base class.

  5. where T : SomeInterface: Specifies that the type argument must be or implement the specified interface.

Constraints help ensure that the generic code is used correctly and provide additional compile-time type safety.

Follow-up 4

How do Generics improve type safety?

Generics improve type safety in several ways:

  1. Compile-time Type Checking: Generics perform type checking at compile time, which helps catch type-related errors before the code is executed. This reduces the chances of runtime errors and improves the overall reliability of the code.

  2. Elimination of Casting: Generics eliminate the need for casting or conversion operations because the type information is known at compile time. This makes the code more readable and less error-prone.

  3. Strongly Typed Collections: Generics allow you to create strongly typed collections, such as List or Dictionary. This ensures that only the specified type of objects can be added to or retrieved from the collection, preventing type mismatches.

By providing compile-time type safety and eliminating the need for casting, generics help write more reliable and maintainable code.

2. How do Generics enhance performance in .NET Framework?

Generics improve performance primarily by eliminating boxing and unboxing for value types and removing the need for runtime type casts. The .NET runtime handles generic instantiation efficiently through JIT specialization.

1. Elimination of boxing/unboxing

Without generics, storing value types (int, struct, etc.) in non-generic collections requires boxing — wrapping the value in a heap-allocated object. This has measurable overhead: heap allocation, GC pressure, and an extra dereference.

// Non-generic: boxing occurs on every Add
var list = new ArrayList();
list.Add(42); // boxes int to object — heap allocation

// Generic: no boxing, value stored directly
var list = new List();
list.Add(42); // no boxing — stored as int

For performance-critical code with large collections of value types, this difference is significant.

2. JIT specialization for value types

The CLR generates specialized native code for each value-type generic instantiation. List and List each get their own optimized machine code. Reference-type instantiations (List, List) share a single implementation (since all references are the same size).

3. Eliminated runtime type checks and casts

Non-generic collections return object, requiring explicit casts that are checked at runtime (and fail with InvalidCastException if wrong). Generics make the types known at compile time — no casts, no runtime checks.

// Non-generic: runtime cast
int val = (int)arrayList[0]; // runtime check every time

// Generic: no cast, type guaranteed at compile time
int val = list[0]; // direct read

4. Fewer allocations, better GC behavior

Avoiding boxing means fewer short-lived objects on the heap, reducing GC pauses. This matters especially in high-throughput scenarios (game loops, request processing, etc.).

5. Compiler and tooling benefits

Generics enable the compiler to catch type mismatches at compile time, removing the need for defensive is/as checks at runtime that add overhead.

Modern additions:

  • Span and Memory are generic types enabling stack-based, allocation-free processing of sequences.
  • Generic math interfaces (INumber, IAdditionOperators, etc.) in .NET 7+ allow writing numeric algorithms once for any numeric type without boxing.
↑ Back to top

Follow-up 1

Can you explain how Generics reduce boxing and unboxing?

Generics reduce boxing and unboxing by allowing the creation of type-specific collection classes. When working with non-generic collection classes, such as ArrayList, value types need to be boxed (converted to objects) before they can be stored in the collection. This boxing operation incurs a performance overhead as it involves creating a new object on the heap and copying the value into it.

With Generics, collection classes can be created to work with specific types, such as List or List. Since these collection classes are type-specific, there is no need for boxing when storing value types. This eliminates the overhead of creating and destroying objects, resulting in improved performance.

For example, consider the following code:

List numbers = new List();
numbers.Add(10);
int sum = numbers[0] + 5;

In this code, the List collection does not require boxing when storing and retrieving integers, leading to improved performance.

Follow-up 2

How do Generics contribute to efficient memory utilization?

Generics contribute to efficient memory utilization in the following ways:

  1. Type-Specific Collections: Generics allow the creation of type-specific collection classes, such as List or Dictionary. These collection classes are optimized to work with specific types, resulting in more efficient memory utilization compared to non-generic collections. This is because type-specific collections do not need to store additional type information for each element, unlike non-generic collections.

  2. Reduced Memory Overhead: Generics eliminate the need for boxing and unboxing when working with value types. Boxing involves creating a new object on the heap, which incurs memory overhead. By avoiding boxing, Generics reduce the memory overhead and improve memory utilization.

  3. Avoidance of Type Conversions: Generics allow the creation of type-specific algorithms and data structures. This eliminates the need for type conversions, which can consume additional memory and impact performance.

Overall, Generics contribute to efficient memory utilization by providing type-specific collections, reducing memory overhead, and avoiding unnecessary type conversions.

Follow-up 3

What is the impact of Generics on runtime performance?

Generics have a positive impact on runtime performance in the .NET Framework. The use of Generics improves performance by providing compile-time type checking, code reusability, reducing boxing and unboxing, and efficient memory utilization.

  1. Compile-Time Type Checking: Generics provide compile-time type checking, which helps in catching type errors at compile-time rather than at runtime. This eliminates the need for runtime type checking, resulting in improved runtime performance.

  2. Code Reusability: Generics allow the creation of reusable code components that can work with multiple types. This reduces the need for duplicate code and improves performance by reducing the amount of code that needs to be compiled and executed.

  3. Reduced Boxing and Unboxing: Generics eliminate the need for boxing and unboxing when working with value types. This improves performance by avoiding the overhead of creating and destroying objects.

  4. Efficient Memory Utilization: Generics allow the creation of collection classes that are type-specific, resulting in more efficient memory utilization compared to non-generic collections.

Overall, Generics have a positive impact on runtime performance by providing type safety, code reusability, reducing boxing and unboxing, and efficient memory utilization.

3. Can you explain the concept of Generic Collections in .NET Framework?

Generic collections are type-safe, high-performance collections in the System.Collections.Generic namespace. They replace the legacy non-generic collections (ArrayList, Hashtable, etc.) and are the standard choice in all modern .NET code.

Core generic collections:

Type Description Use case
List Resizable array General-purpose ordered list
Dictionary Hash map Key-value lookups, O(1) average
HashSet Unique value set Deduplication, set operations
Queue FIFO collection Task queues, message buffers
Stack LIFO collection Undo stacks, expression parsing
SortedList Sorted key-value pairs Range queries, sorted iteration
SortedDictionary Balanced BST key-value Frequent insertions with sorted access
LinkedList Doubly linked list Frequent insertions/deletions at arbitrary positions

Modern additions (recent .NET versions):

  • ImmutableList, ImmutableDictionary, etc. (System.Collections.Immutable) — thread-safe immutable collections.
  • ConcurrentDictionary, ConcurrentQueue, ConcurrentBag (System.Collections.Concurrent) — thread-safe mutable collections.
  • FrozenDictionary, FrozenSet (.NET 8+) — optimized read-only collections with the fastest possible lookup performance.
  • PriorityQueue (.NET 6+) — min-heap priority queue.

Example:

var scores = new Dictionary
{
    ["Alice"] = 95,
    ["Bob"] = 87,
};

scores["Carol"] = 92;

foreach (var (name, score) in scores)
    Console.WriteLine($"{name}: {score}");

// HashSet for deduplication
var visited = new HashSet();
visited.Add("page1");
bool isNew = visited.Add("page1"); // false — already present

Key interfaces:

  • IEnumerable – enables foreach and LINQ.
  • ICollection – adds Count, Add, Remove, Contains.
  • IList – adds index-based access.
  • IReadOnlyList / IReadOnlyDictionary – expose read-only views.

Always program to interfaces (IList, IDictionary) in method signatures for flexibility.

↑ Back to top

Follow-up 1

What are the advantages of Generic Collections over non-generic collections?

There are several advantages of using generic collections over non-generic collections:

  1. Type Safety: Generic collections ensure type safety at compile-time, which means that you cannot insert an item of the wrong type into the collection. This helps to catch errors early and improves the reliability of your code.

  2. Performance: Generic collections are more efficient than non-generic collections because they eliminate the need for boxing and unboxing operations. Boxing is the process of converting a value type to a reference type, and unboxing is the reverse process. These operations can be expensive in terms of memory and CPU usage.

  3. Code Reusability: Generic collections promote code reusability because they can work with any data type. You can write generic algorithms and data structures that can be used with different types without modification.

  4. Readability: Generic collections make your code more readable and maintainable by providing type information at the point of use.

Follow-up 2

Can you provide an example of using a Generic Collection?

Sure! Here's an example of using the List class, which is a commonly used generic collection in .NET Framework:

List names = new List();
names.Add("John");
names.Add("Jane");
names.Add("Bob");

foreach (string name in names)
{
    Console.WriteLine(name);
}

In this example, we create a List to store a collection of strings. We then add three names to the list using the Add method. Finally, we iterate over the list using a foreach loop and print each name to the console.

Follow-up 3

What are some commonly used Generic Collections in .NET Framework?

There are several commonly used generic collections in .NET Framework. Some of them are:

  1. List: Represents a dynamic list of elements.

  2. Dictionary: Represents a collection of key-value pairs.

  3. Queue: Represents a first-in, first-out (FIFO) collection of elements.

  4. Stack: Represents a last-in, first-out (LIFO) collection of elements.

  5. LinkedList: Represents a doubly linked list of elements.

These are just a few examples, and there are many more generic collections available in .NET Framework to suit different needs.

4. How do you implement constraints in Generics?

Generic constraints restrict what types can be used as type arguments, enabling the generic code to safely use members of the constrained type. They are declared with the where keyword.

Available constraint types:

Constraint Meaning
where T : class T must be a reference type
where T : struct T must be a non-nullable value type
where T : new() T must have a public parameterless constructor
where T : SomeBaseClass T must inherit from SomeBaseClass
where T : ISomeInterface T must implement ISomeInterface
where T : U T must be or derive from another type parameter U
where T : notnull T must be non-nullable (C# 8+)
where T : unmanaged T must be an unmanaged type (no managed references)
where T : allows ref struct T can be a ref struct (C# 13+)

Examples:

// Must implement IComparable
public T Max(T a, T b) where T : IComparable
    =&gt; a.CompareTo(b) &gt;= 0 ? a : b;

// Must be a class with a parameterless constructor
public T CreateInstance() where T : class, new()
    =&gt; new T();

// Repository: must be a reference type implementing an interface
public class Repository where T : class, IEntity
{
    public void Save(T entity) { /* ... */ }
}

// Multiple constraints on multiple type parameters
public class Converter
    where TInput : IConvertible
    where TOutput : struct
{
    public TOutput Convert(TInput input) { /* ... */ }
}

Generic math constraints (.NET 7+):

.NET 7 introduced numeric interfaces in System.Numerics, enabling generic numeric algorithms:

using System.Numerics;

public T Sum(IEnumerable values) where T : INumber
    =&gt; values.Aggregate(T.Zero, (acc, v) =&gt; acc + v);

// Works for int, double, decimal, float, etc.
int total = Sum(new[] { 1, 2, 3 });
double avg = Sum(new[] { 1.5, 2.5 });

Why constraints matter:

Without constraints, a generic type parameter is treated as object — you can only call methods defined on object. Constraints unlock the members of the constrained type, while the compiler still enforces type safety.

↑ Back to top

Follow-up 1

What are the different types of constraints in Generics?

There are several types of constraints that can be used in Generics:

  1. Type parameter must be a reference type: where T : class
  2. Type parameter must be a value type: where T : struct
  3. Type parameter must have a default constructor: where T : new()
  4. Type parameter must be a specific base class: where T : MyBaseClass
  5. Type parameter must implement a specific interface: where T : IMyInterface
  6. Type parameter must be a specific type: where T : SomeType

These constraints can be combined using the 'where' keyword. For example:

public class MyClass where T : class, IMyInterface, new()
{
    // Class implementation
}

Follow-up 2

Can you provide an example of using constraints in Generics?

Sure! Here's an example of using constraints in Generics:

public class GenericClass where T : IComparable
{
    public bool IsGreaterThan(T value1, T value2)
    {
        return value1.CompareTo(value2) &gt; 0;
    }
}

GenericClass intClass = new GenericClass();
bool result = intClass.IsGreaterThan(5, 3);
// result will be true

GenericClass stringClass = new GenericClass();
result = stringClass.IsGreaterThan("abc", "def");
// result will be false

Follow-up 3

What happens if a constraint is not met in Generics?

If a constraint is not met in Generics, a compile-time error will occur. For example, if you have a constraint that requires the type parameter to implement a specific interface, and you try to use a type that does not implement that interface, the compiler will generate an error. This helps ensure type safety and prevents runtime errors related to invalid type arguments.

5. Can you explain the concept of Generic Delegates in .NET Framework?

Generic delegates are delegate types parameterized with type parameters, allowing them to represent methods with varying signatures without declaring a new delegate type for each one. .NET provides built-in generic delegate types that cover almost all scenarios.

Built-in generic delegates:

Action — represents a method that returns void:

Action print = () =&gt; Console.WriteLine("Hello");
Action greet = name =&gt; Console.WriteLine($"Hello, {name}");
Action logSum = (a, b) =&gt; Console.WriteLine(a + b);

print();           // Hello
greet("Alice");    // Hello, Alice
logSum(3, 4);      // 7

Func — represents a method that returns a value:

Func getAnswer = () =&gt; 42;
Func add = (a, b) =&gt; a + b;
Func isLong = s =&gt; s.Length &gt; 10;

int result = add(3, 4);     // 7
bool check = isLong("Hi");  // false

Predicate — equivalent to Func, used for filtering:

Predicate isEven = n =&gt; n % 2 == 0;
bool even = isEven(4); // true

Using in LINQ and higher-order functions:

var numbers = Enumerable.Range(1, 10);

// Func used as predicate
var evens = numbers.Where(n =&gt; n % 2 == 0);

// Func for projection
var squares = numbers.Select(n =&gt; n * n);

// Action for side effects
numbers.ToList().ForEach(n =&gt; Console.WriteLine(n));

Custom generic delegates (rarely needed):

// Custom delegate when Action/Func don't fit (e.g., out/ref parameters)
public delegate bool TryParse(string input, out T result);

TryParse parser = int.TryParse;
parser("42", out int val); // val = 42

EventHandler:

A generic delegate specifically for the event pattern:

public event EventHandler? OrderPlaced;
// Signature: void(object sender, OrderEventArgs e)

Key point: Action and Func support up to 16 type parameters, covering virtually every method signature. Always prefer them over declaring custom delegate types.

↑ Back to top

Follow-up 1

How do Generic Delegates differ from regular delegates?

Regular delegates in .NET Framework are defined with a specific signature, meaning they can only be used with methods that have the exact same parameter types and return type. Generic Delegates, on the other hand, can be used with methods that have different parameter types and return types. This makes them more flexible and reusable.

Follow-up 2

Can you provide an example of a Generic Delegate?

Sure! Here's an example of using the Action delegate, which is a generic delegate that does not return a value:

Action printNumber = (number) =&gt; Console.WriteLine(number);
printNumber(10);

Follow-up 3

What are the advantages of using Generic Delegates?

There are several advantages of using Generic Delegates in .NET Framework:

  1. Reusability: Generic Delegates can be used with methods that have different parameter types and return types, making them more reusable.
  2. Type Safety: Generic Delegates provide type safety at compile time, ensuring that the correct types are used with the delegate.
  3. Code Readability: By using Generic Delegates, the code becomes more readable and expressive, as the delegate's purpose is clear from its type parameters.
  4. Reduced Boilerplate Code: Generic Delegates eliminate the need for creating multiple delegates with different signatures, reducing the amount of boilerplate code in the application.

Live mock interview

Mock interview: Understanding Generics

Intermediate ~5 min Your own free AI key

Your voice and your AI key never touch our servers; the key stays in this browser and is sent only to Google. Only your round scores are saved to track progress.