C# Arrays and Collections Explained — From Zero to Confident
- Arrays are fixed-size and zero-indexed — slot 0 is first, slot (Length-1) is last. Use them for data that never changes in size.
- List<T> is your everyday workhorse — it resizes itself, supports Add/Remove/Contains, and uses .Count (not .Length) to tell you how many items it holds.
- Dictionary<TKey, TValue> gives you near-instant lookup by a meaningful key — use
TryGetValue()instead of the indexer to avoid crashes on missing keys.
Imagine you're organising a birthday party. A single variable is like holding one balloon in your hand — great for one thing. An array is like a balloon rack with fixed, numbered slots — you decide upfront you need exactly 10 balloons, and each slot has a number starting at zero. A List is like a stretchy gift bag — you can keep stuffing more presents in without deciding the size in advance. A Dictionary is like a labelled coat-check counter — instead of a number, each item has a unique name tag (like 'Alice's coat') so you can grab exactly the right one instantly.
Every real app — from a shopping cart to a leaderboard to a contact book — needs to store more than one piece of data at a time. The moment you move beyond a single variable, you need a structure that can hold a group of values and let you work with them together. That's exactly what arrays and collections do, and they are arguably the most-used tools in any C# developer's daily work. Knowing them well separates a developer who struggles with basic data wrangling from one who writes clean, confident code.
Before collections existed, developers had to manually juggle groups of related data — declaring ten separate variables for ten students, copying data into new arrays whenever the size changed, writing fragile code that broke if you added one extra item. Arrays gave us numbered slots in memory. The Collections namespace in .NET went further, giving us dynamic, purpose-built containers like List, Dictionary, Queue, and Stack that handle growth, lookup, and ordering automatically.
By the end of this article you'll be able to declare and use arrays, create and manipulate List<T> and Dictionary<TKey, TValue>, iterate over any collection with a foreach loop, choose the right container for a given problem, and avoid the three classic mistakes that trip up almost every beginner. Let's build this up from scratch.
C# Arrays — Fixed-Size Storage With Numbered Slots
An array is the simplest way to store multiple values of the same type. Think of it as a row of labelled lockers in a school corridor. You decide how many lockers you need when you build the corridor — that number never changes. Each locker has a number starting at zero (not one — this trips people up constantly). Locker zero is the first one.
You declare an array by writing the type, square brackets, a name, and then using new to create it with a fixed size. Once created, every slot is automatically filled with the default value for that type — zero for numbers, null for strings.
Arrays are the right choice when you know exactly how many items you'll have and that number won't change — days of the week, months of the year, RGB colour channels. They are blazing fast because the computer lays them out in a straight line in memory. But if the size might change — use a List instead, which we'll cover next.
The .Length property tells you how many slots exist. Accessing a slot that doesn't exist (like index 10 in a 5-slot array) throws an IndexOutOfRangeException — one of the most common crashes beginners see.
using System; namespace io.thecodeforge.collections { class ArrayBasics { static void Main() { // 1. Fixed-size allocation for weekdays string[] weekdays = new string[5]; weekdays[0] = "Monday"; weekdays[1] = "Tuesday"; weekdays[2] = "Wednesday"; weekdays[3] = "Thursday"; weekdays[4] = "Friday"; // 2. Shorthand syntax int[] highScores = { 9800, 7650, 6200, 5100, 3400 }; Console.WriteLine($"First weekday: {weekdays[0]}"); Console.WriteLine($"Top score: {highScores[0]}"); Console.WriteLine("\n--- High Score Leaderboard ---"); for (int i = 0; i < highScores.Length; i++) { Console.WriteLine($"#{i + 1}: {highScores[i]}"); } } } }
Top score: 9800
--- High Score Leaderboard ---
#1: 9800
#2: 7650
#3: 6200
#4: 5100
#5: 3400
array.Length - 1 if you need the last item, or just use array[^1] in modern C# (version 8+).List — The Dynamic Array That Grows With You
The moment you need a collection whose size isn't fixed — a shopping cart that can have any number of items, a list of players that join and leave — an array becomes a headache. You'd have to create a new, bigger array and copy everything over every time you wanted to add an item. That's exactly the problem List<T> solves.
List<T> is a generic collection. The T is a placeholder for the type you want to store — List<string> stores strings, List<int> stores integers. Think of it as an array with superpowers: it resizes itself automatically, gives you ., Add()., Remove()., Contains().Count, and dozens of other useful methods out of the box.
Under the hood, a List actually is backed by an array — it just handles all the resizing work for you. When it runs out of room, it silently creates a new internal array twice the size and copies everything across. You never have to think about this, but it's good to know so you understand why arrays are marginally faster for fixed data.
Use List<T> as your default collection whenever the size might change or you don't know it upfront. It's the workhorse of C# development — you'll use it in virtually every project you write.
using System; using System.Collections.Generic; namespace io.thecodeforge.collections { class ListBasics { static void Main() { // 1. Dynamic list for a shopping cart List<string> shoppingCart = new List<string>(); shoppingCart.Add("Apples"); shoppingCart.Add("Bread"); shoppingCart.Add("Milk"); Console.WriteLine($"Items in cart: {shoppingCart.Count}"); // 2. Removing and checking existence shoppingCart.Remove("Bread"); bool hasMilk = shoppingCart.Contains("Milk"); // 3. Sorting in-place List<int> scores = new List<int> { 90, 10, 50, 30 }; scores.Sort(); Console.WriteLine("Sorted scores:"); scores.ForEach(s => Console.Write(s + " ")); } } }
Sorted scores:
10 30 50 90
.Length. Lists use .Count. They do the same thing — tell you how many items are stored — but using the wrong one is a compiler error that catches out almost every beginner on their first day with collections. The rule is simple: if it ends in [] it's an array, use .Length. If it's a List<T>, use .Count.Dictionary — Lightning-Fast Lookup by Name
Sometimes a numbered index isn't the right way to identify data. You don't want player stats at index 3 — you want them by player name. You don't want a country's capital at index 47 — you want it by the country's name. That's what Dictionary<TKey, TValue> is for.
A dictionary stores key-value pairs. Every item has a unique key (like a name, an ID, a code) and a value (the data attached to that key). Think of a real physical dictionary: you look up a word (the key) and get the definition (the value) instantly — you don't have to read every page to find it.
Lookup in a dictionary is extremely fast — essentially instant regardless of how many items are stored — because .NET uses a technique called hashing internally. Compare this to searching through a List where, in the worst case, you have to check every single item.
Keys must be unique. You can't have two entries with the key "Alice" — the second add will throw an exception. Values can be duplicated — two players can have the same score. Use ContainsKey() before adding if you're not sure whether the key exists yet.
Use a Dictionary whenever you need to look things up by a meaningful name or ID rather than a position number.
using System; using System.Collections.Generic; namespace io.thecodeforge.collections { class DictionaryBasics { static void Main() { // 1. Storing prices by product SKU var priceLookup = new Dictionary<string, decimal> { { "APP-01", 1.99m }, { "BRD-02", 2.50m } }; // 2. Safe retrieval with TryGetValue string targetSku = "MLK-03"; if (priceLookup.TryGetValue(targetSku, out decimal price)) { Console.WriteLine($"Price of {targetSku}: {price}"); } else { Console.WriteLine("Product not found."); } // 3. Iterating over keys and values foreach (var entry in priceLookup) { Console.WriteLine($"SKU: {entry.Key}, Price: {entry.Value}"); } } } }
SKU: APP-01, Price: 1.99
SKU: BRD-02, Price: 2.50
SortedDictionary<TKey, TValue> instead.| Feature / Aspect | Array (string[]) | List<T> | Dictionary<TKey, TValue> |
|---|---|---|---|
| Size | Fixed at creation — cannot grow | Dynamic — grows automatically | Dynamic — grows automatically |
| Access method | By index: arr[0] | By index: list[0] | By key: dict["name"] |
| Add items after creation | Not possible without new array | list.Add(item) — easy | dict.Add(key, value) |
| Remove items | Not possible — must create new array | list.Remove(item) — easy | dict.Remove(key) |
| Check if item exists | Manual loop required | list.Contains(item) — O(n) | dict.ContainsKey(k) — O(1) fast |
| Duplicate values | Allowed | Allowed | Keys must be unique; values can duplicate |
| Preserves insertion order | Yes | Yes | Not guaranteed |
| Best used for | Fixed-size, performance-critical data | Default go-to for any ordered list | Fast lookup by meaningful key |
| Property for item count | .Length | .Count | .Count |
🎯 Key Takeaways
- Arrays are fixed-size and zero-indexed — slot 0 is first, slot (Length-1) is last. Use them for data that never changes in size.
- List<T> is your everyday workhorse — it resizes itself, supports Add/Remove/Contains, and uses .Count (not .Length) to tell you how many items it holds.
- Dictionary<TKey, TValue> gives you near-instant lookup by a meaningful key — use
TryGetValue()instead of the indexer to avoid crashes on missing keys. - Never add to or remove from a List<T> inside a foreach loop — it throws at runtime. Collect changes and apply them after the loop.
⚠ Common Mistakes to Avoid
Interview Questions on This Topic
- QExplain the internal behavior of List<T> when it exceeds its current capacity. What is the time complexity of adding an element (Amortized O(1))?
- QWhy does a Dictionary lookup take O(1) time? Discuss the role of
GetHashCode()andEquals()in key retrieval. - QWhat is the difference between IEnumerable<T>, ICollection<T>, and IList<T>? When would you use one over the other in a function signature?
- QHow do you implement a Two-Sum problem (LeetCode standard) efficiently using a Dictionary in C#?
- QWhat is the difference between a Dictionary and a ConcurrentDictionary in a multi-threaded ASP.NET Core application?
Frequently Asked Questions
What is the difference between an array and a List in C#?
An array has a fixed size you set at creation and cannot change — it's fast and simple but inflexible. A List<T> resizes itself automatically as you add or remove items and comes with helpful methods like Add(), Remove(), and Contains(). Use an array when the size is known and constant; use a List when it isn't.
How do I avoid a KeyNotFoundException when reading from a C# Dictionary?
Instead of accessing a value directly with dict[key] (which throws if the key is missing), use dict.TryGetValue(key, out var value). It returns true if the key was found and puts the value in the value variable, or returns false without throwing any exception if the key doesn't exist.
Can a C# List hold different types — like a mix of strings and numbers?
Not directly with a typed List<T>. A List<string> can only hold strings and a List<int> can only hold integers — the compiler enforces this, which prevents bugs. If you genuinely need mixed types, you can use List<object>, but this loses type safety and requires casting, so it's usually a sign you need a better data structure like a class or record instead.
Which C# collection is best for implementing a First-In-First-Out (FIFO) queue?
The Queue<T> class is specifically designed for FIFO operations. It provides an Enqueue() method to add items to the back and a Dequeue() method to remove items from the front, ensuring O(1) performance for these operations unlike a List which would require shifting elements.
Developer and founder of TheCodeForge. I built this site because I was tired of tutorials that explain what to type without explaining why it works. Every article here is written to make concepts actually click.