IList

3 min read
Foundational2 min read
Rapid overview

IList

TL;DR

IList<T> extends ICollection<T> with an indexer plus Insert, RemoveAt, and IndexOf, exposing random access at index i. Use it when callers truly need positional reads or insert-at-position semantics (an order book, a price ladder); otherwise prefer the narrower IEnumerable<T> or IReadOnlyList<T> so you don't leak mutability that the API never intended to support.

How it works

IList — Indexed Access Collections

"Use IList when you need random access by index, or when Insert/RemoveAt operations are required."

❌ Bad example:

public IEnumerable<Trade> GetRecentTrades()
{
    return _trades.OrderByDescending(t => t.Timestamp).Take(10);
}

// Caller
public void DisplayLastTrade(IEnumerable<Trade> trades)
{
    var lastTrade = trades.Last(); // enumerates entire sequence
    Console.WriteLine(lastTrade.Symbol);
}

Using .Last() on IEnumerable requires full enumeration—inefficient.

âś… Good example:

public IList<Trade> GetRecentTrades()
{
    return _trades.OrderByDescending(t => t.Timestamp).Take(10).ToList();
}

// Caller
public void DisplayLastTrade(IList<Trade> trades)
{
    var lastTrade = trades[trades.Count - 1]; // O(1) indexed access
    Console.WriteLine(lastTrade.Symbol);
}

👉 IList enables O(1) random access via indexer, avoiding enumeration.

🔥 When Insert/RemoveAt matters:

public class OrderBook
{
    public IList<Order> BuyOrders { get; } = new List<Order>();

    public void InsertOrderAtPrice(Order order)
    {
        // Binary search to find insertion point
        int index = BuyOrders.BinarySearch(order, new PriceComparer());
        if (index < 0) index = ~index;

        BuyOrders.Insert(index, order); // insert at specific position
    }
}

👉 IList supports Insert() and RemoveAt() for positional mutations.

🔥 Avoiding unnecessary copies:

// ❌ Bad: forces caller to know concrete type
public List<decimal> CalculatePrices(List<decimal> basePrices)
{
    for (int i = 0; i < basePrices.Count; i++)
        basePrices[i] *= 1.05m;
    return basePrices;
}

// âś… Good: accepts interface, returns interface
public IList<decimal> CalculatePrices(IList<decimal> basePrices)
{
    for (int i = 0; i < basePrices.Count; i++)
        basePrices[i] *= 1.05m;
    return basePrices;
}

👉 Accepting IList allows arrays or lists without forcing specific types.

đź’ˇ In trading systems:

  • Use IList for order books where indexed access is critical for price levels.
  • Enable efficient batch processing with index-based loops (faster than foreach for large arrays).
  • Prefer IEnumerable unless random access is genuinely needed—narrower contract.

Quick recall Q&A

Q: What's the difference between ICollection and IList? A: IList extends ICollection and adds indexed access (this[int]), Insert(), RemoveAt(), and IndexOf(). Use IList when position matters.
Q: Does IList guarantee O(1) indexed access? A: Not strictly. List and arrays are O(1), but custom implementations (e.g., linked lists) could be O(n). Documentation should clarify performance.
Q: Should I return IList from methods? A: Only if callers need indexed access or Insert/RemoveAt. Otherwise, IEnumerable or IReadOnlyList are better—narrower contracts prevent misuse.
Q: Can IList be read-only? A: Yes. Arrays are fixed-size, so Add/Remove throw NotSupportedException. Check IsReadOnly before assuming mutability. Prefer IReadOnlyList for clarity.
Q: How does IList relate to arrays? A: Arrays implement IList, providing O(1) indexed access. They're IList but fixed-size, so Add/Remove fail. Use Array.AsReadOnly() for IReadOnlyList.
Q: When should I use for vs foreach with IList? A: Use for (int i = 0; i < list.Count; i++) when you need the index or modify elements by index. Use foreach for clarity when index isn't needed.
Q: What's the performance of IndexOf() on IList? A: O(n) for List (linear search). If frequent lookups are needed, use Dictionary or HashSet instead.
Q: Can I pass an array as IList? A: Yes. Arrays implement IList, but Add/Remove throw exceptions. This enables accepting both arrays and lists without overloads.
Q: How do I mock IList in tests? A: Use List or arrays directly for simple cases. For complex scenarios, mocking frameworks (Moq, NSubstitute) can stub IList behavior.
Q: What happens if I access IList[index] out of bounds? A: Throws IndexOutOfRangeException (arrays) or ArgumentOutOfRangeException (List). Always validate index < Count before accessing.

See also