Generics
C# Programming
List Interface
.NET Framework
Interface Implementation

Why does does it really? ListT implement all these interfaces, not just IListT?

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

List<T> in .NET implements more than IList<T> because different APIs care about different levels of capability. Some only need enumeration, some need read-only count access, some need indexed mutation, and some still work through older non-generic collection interfaces. Implementing the broader interface set lets one concrete type participate cleanly in all of those contexts.

Interface Inheritance Explains Part of It

The first thing to notice is that some of the interface list is inherited structurally, not added independently. IList<T> already implies other generic interfaces such as ICollection<T> and IEnumerable<T>.

That means if a type implements IList<T>, it necessarily also supports the contracts required by those parent interfaces.

A simple example:

csharp
1using System;
2using System.Collections.Generic;
3
4class Program
5{
6    static void PrintAll(IEnumerable<int> values)
7    {
8        foreach (var value in values)
9        {
10            Console.WriteLine(value);
11        }
12    }
13
14    static void Main()
15    {
16        List<int> numbers = new List<int> { 1, 2, 3 };
17        PrintAll(numbers);
18    }
19}

PrintAll only needs IEnumerable<int>, but a List<int> can be passed directly because it fulfills that smaller contract too.

Why Separate Interfaces Still Matter

If interface inheritance explains part of the story, why does List<T> explicitly expose so many interfaces in documentation and metadata? Because .NET collection design is layered.

Different interfaces represent different promises:

  • 'IEnumerable<T> means you can iterate.'
  • 'ICollection<T> means there is a size and basic add or remove behavior.'
  • 'IList<T> means indexed access and insertion semantics.'
  • 'IReadOnlyCollection<T> means count without mutation.'
  • 'IReadOnlyList<T> means indexed reads without mutation.'

This layering lets APIs ask for the least power they need. That improves both clarity and flexibility.

Read-Only Interfaces Are Not Redundant

One subtle point is that read-only interfaces are not pointless just because List<T> is mutable. They matter because a mutable object can still be viewed through a read-only contract.

csharp
1using System;
2using System.Collections.Generic;
3
4class Program
5{
6    static void PrintFirst(IReadOnlyList<string> items)
7    {
8        if (items.Count > 0)
9        {
10            Console.WriteLine(items[0]);
11        }
12    }
13
14    static void Main()
15    {
16        var names = new List<string> { "Ana", "Ben", "Chen" };
17        PrintFirst(names);
18    }
19}

PrintFirst does not need insertion or deletion. Accepting IReadOnlyList<string> advertises that fact clearly, and List<string> can still supply the data.

Why Non-Generic Interfaces Still Exist

List<T> also implements older non-generic interfaces such as IList, ICollection, and IEnumerable. That is mostly about compatibility with older parts of the .NET ecosystem.

Legacy APIs, reflection-heavy frameworks, older libraries, and tooling written before generics may still interact through these interfaces. If List<T> ignored them entirely, it would be harder to interoperate with existing code.

This compatibility is not glamorous, but it is practical library design.

Concrete Types Should Be Widely Usable

A standard library collection is more valuable when it works in many contexts without adapters. That is the real reason List<T> implements a broad interface surface. It is not trying to look clever. It is trying to be usable everywhere a dynamic indexed collection makes sense.

Consider these signatures:

csharp
1void A(IEnumerable<int> values)
2void B(ICollection<int> values)
3void C(IList<int> values)
4void D(IReadOnlyList<int> values)

A single List<int> instance can be passed to all four. That reduces friction across the framework.

Why API Authors Should Usually Prefer Interfaces

This topic also explains a common .NET design guideline: accept the narrowest interface your method actually needs.

If your method only enumerates items, use IEnumerable<T>. If it needs indexing but not mutation, use IReadOnlyList<T>. If it must modify the collection shape, use IList<T> or perhaps a more specialized abstraction.

That keeps your code decoupled from one concrete collection type.

csharp
1int SumFirstTwo(IReadOnlyList<int> values)
2{
3    if (values.Count < 2)
4    {
5        throw new ArgumentException("Need at least two values.");
6    }
7
8    return values[0] + values[1];
9}

A List<int> works here, but so would an array or another compatible collection.

It Does Not Mean Every Interface Is Equally Important

Documentation can make the interface list look overwhelming, but not all interfaces carry the same design weight. Some exist because of inheritance, some for compatibility, and some for read-only usage patterns.

The important mental model is not "why so many random interfaces." It is "which contracts does this collection satisfy, and why would an API choose one contract over another."

Once you think in contracts instead of concrete classes, the design makes more sense.

Common Pitfalls

A common mistake is assuming that because List<T> implements IReadOnlyList<T>, it becomes immutable. It does not. The object is still mutable; only the reference type used by the caller is restricted.

Another issue is exposing List<T> in public APIs when you only need enumeration. That couples callers to a specific implementation for no gain.

Developers also sometimes ignore older non-generic interfaces as useless clutter. In framework code, compatibility layers are often deliberate and necessary.

Finally, do not confuse interface count with complexity of everyday usage. Most code uses only a small subset of these contracts.

Summary

  • 'List<T> implements multiple interfaces because different APIs require different collection capabilities.'
  • Some interfaces come from inheritance, not separate design choices.
  • Read-only interfaces matter even for mutable collections because they restrict what callers can do.
  • Non-generic interfaces remain for compatibility with older .NET code.
  • Library code should usually accept the narrowest interface it needs.
  • Thinking in terms of contracts makes the interface list much less mysterious.

Course illustration
Course illustration

All Rights Reserved.