Lists
List<T> is the default mutable collection — dynamic array backing with amortized O(1) Add.
Introduction
List<T> is the default mutable collection — dynamic array backing with amortized O(1) Add. It implements IList
Capacity doubles on growth — understanding this helps memory profiling. ReadOnlyCollection wraps List for exposing immutable views. Interviewers ask List vs LinkedList (rare in .NET LOB) and thread safety (List is not thread-safe).
The story
An online electronics store builds a shopping cart as a list of line items — each with a SKU, quantity, and unit price. Customers add items throughout their session; the cart computes a running total and can show the three most expensive products before checkout.
Lists grow dynamically unlike arrays, which is why carts, wish lists, and in-memory order drafts use List<T> everywhere in e-commerce backends.
Understanding the topic
Key concepts
- List
resizable array implementation. - Add, Insert, Remove, RemoveAt, Clear operations.
- Capacity vs Count — pre-size with capacity constructor.
- Find, FindAll, Sort, BinarySearch on sorted lists.
- Not thread-safe — use ConcurrentBag or lock.
- Collection expressions can create List
with [items].
Step-by-step explanation
- Add appends at Count; grows capacity when full.
- Insert shifts elements — O(n).
- Remove scans for equality — O(n).
- Sort uses introspective sort O(n log n).
- foreach uses struct enumerator — minimal allocation.
- ToList materializes LINQ query into List.
Practical code example
Order line items with List operations and capacity hint:
namespace TechLearningPro.Lists;public sealed record LineItem(string Sku, int Quantity, decimal UnitPrice);public sealed class Order{private readonly List<LineItem> _lines = new(capacity: 8);public IReadOnlyList<LineItem> Lines => _lines;public void AddLine(LineItem item){ArgumentNullException.ThrowIfNull(item);_lines.Add(item);}public decimal Total => _lines.Sum(l => l.Quantity * l.UnitPrice);public List<LineItem> TopExpensiveLines(int n) =>_lines.OrderByDescending(l => l.UnitPrice).Take(n).ToList();}
Line-by-line code explanation
sealed record LineItem(string Sku, int Quantity, decimal UnitPrice)models one cart row.private readonly List<LineItem> _lines = new(capacity: 8)pre-allocates space for typical cart sizes.public IReadOnlyList<LineItem> Lines => _linesexposes a read-only view — callers cannot bypassAddLine.AddLine(LineItem item)is the controlled way to mutate the cart.ArgumentNullException.ThrowIfNull(item)rejects null items at the API boundary._lines.Add(item)appends the item to the internal list.Total => _lines.Sum(l => l.Quantity * l.UnitPrice)computes cart total with LINQ.OrderByDescending(l => l.UnitPrice)sorts lines by price descending.Take(n).ToList()materializes the top N expensive lines into a new list.
Key takeaway: Expose IReadOnlyList externally; mutate via methods. capacity: 8 avoids early reallocations for typical cart sizes.
Real-world use
Where you'll use this in production
- In-memory shopping carts before checkout.
- Aggregating API page results client-side.
- Building CSV export rows.
- Caching hot lookup lists in memory.
Best practices
- Expose IReadOnlyList, keep List private.
- Preallocate capacity when size estimate known.
- Use RemoveAll with predicate vs manual loop.
- Sort once before repeated BinarySearch.
- Consider ImmutableList for shared concurrent reads.
Common mistakes
- Modifying list during foreach — InvalidOperationException.
- Returning internal List allowing external mutation.
- LinkedList for general use — poor cache locality.
- Contains on large lists — use HashSet for lookups.
Advanced interview questions
Q1BeginnerList growth strategy?
Q2BeginnerList thread-safe?
Q3IntermediateList vs IList interface?
Q4IntermediateRemove item while iterating?
Q5AdvancedDesign paginated in-memory cache of 1M items.
Summary
List