Skip to content
Home C# / .NET C# Arrays and Collections Explained — From Zero to Confident

C# Arrays and Collections Explained — From Zero to Confident

Where developers are forged. · Structured learning · Free forever.
📍 Part of: C# Basics → Topic 5 of 11
C# arrays and collections explained for beginners — learn arrays, Lists, Dictionaries and when to use each, with runnable code and real output.
🧑‍💻 Beginner-friendly — no prior C# / .NET experience needed
In this tutorial, you'll learn
C# arrays and collections explained for beginners — learn arrays, Lists, Dictionaries and when to use each, with runnable code and real output.
  • 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.
✦ Plain-English analogy ✦ Real code with output ✦ Interview questions
Quick Answer

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.

io/thecodeforge/collections/ArrayBasics.cs · CSHARP
123456789101112131415161718192021222324252627282930
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]}");
            }
        }
    }
}
▶ Output
First weekday: Monday
Top score: 9800

--- High Score Leaderboard ---
#1: 9800
#2: 7650
#3: 6200
#4: 5100
#5: 3400
⚠ Watch Out: Indexes Start at Zero, Always
The first element is always at index 0, not 1. A 5-element array has valid indexes 0 through 4. Trying to access index 5 throws an IndexOutOfRangeException at runtime — the compiler won't catch this for you. Always use 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.

io/thecodeforge/collections/ListBasics.cs · CSHARP
123456789101112131415161718192021222324252627282930
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 + " "));
        }
    }
}
▶ Output
Items in cart: 3
Sorted scores:
10 30 50 90
💡Pro Tip: Use .Count, Not .Length on a List
Arrays use .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.

io/thecodeforge/collections/DictionaryBasics.cs · CSHARP
1234567891011121314151617181920212223242526272829303132333435
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}");
            }
        }
    }
}
▶ Output
Product not found.
SKU: APP-01, Price: 1.99
SKU: BRD-02, Price: 2.50
⚠ Watch Out: Dictionaries Don't Guarantee Order
A Dictionary<TKey, TValue> does not store items in the order you added them. If you need items in insertion order, use a List<T> or in .NET 5+ you can rely on Dictionary maintaining insertion order as an implementation detail — but never depend on it as a guarantee. If ordered key-value pairs matter, use SortedDictionary<TKey, TValue> instead.
Feature / AspectArray (string[])List<T>Dictionary<TKey, TValue>
SizeFixed at creation — cannot growDynamic — grows automaticallyDynamic — grows automatically
Access methodBy index: arr[0]By index: list[0]By key: dict["name"]
Add items after creationNot possible without new arraylist.Add(item) — easydict.Add(key, value)
Remove itemsNot possible — must create new arraylist.Remove(item) — easydict.Remove(key)
Check if item existsManual loop requiredlist.Contains(item) — O(n)dict.ContainsKey(k) — O(1) fast
Duplicate valuesAllowedAllowedKeys must be unique; values can duplicate
Preserves insertion orderYesYesNot guaranteed
Best used forFixed-size, performance-critical dataDefault go-to for any ordered listFast 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

    Off-by-one index errors — Accessing `array[array.Length]` instead of `array[array.Length - 1]` for the last element.
    Symptom

    IndexOutOfRangeException at runtime.

    Fix

    Remember arrays are zero-indexed, so a 5-element array has valid indexes 0–4. Use array[^1] in C# 8+ for the last element, or array[array.Length - 1] in older versions.

    Adding a duplicate key to a Dictionary — Calling `dictionary.Add(existingKey, value)` when that key already exists throws `ArgumentException: An item with the same key has already been added`. Fix: Always check first with `if (!dict.ContainsKey(key))` before calling `.Add()`, or use the indexer `dict[key] = value` which safely overwrites an existing entry instead of throwing.
    Fix

    Always check first with if (!dict.ContainsKey(key)) before calling .Add(), or use the indexer dict[key] = value which safely overwrites an existing entry instead of throwing.

    Modifying a List while iterating over it with foreach — Adding or removing items inside a `foreach` loop throws `InvalidOperationException: Collection was modified; enumeration operation may not execute`. Fix: Either iterate over a copy (`foreach (var item in myList.ToList())`), or collect items to remove in a separate list and remove them after the loop finishes.
    Fix

    Either iterate over a copy (foreach (var item in myList.ToList())), or collect items to remove in a separate list and remove them after the loop finishes.

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() and Equals() 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.

🔥
Naren Founder & Author

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.

← PreviousMethods and Parameters in C#Next →Strings in C#
Forged with 🔥 at TheCodeForge.io — Where Developers Are Forged