Collections
A collection keeps many values under one name. .NET has ready-made ones in System.Collections.Generic. This program uses the two you will use most: List<T>, values in order, and Dictionary<TKey, TValue>, values found by a key.
List<T>: an array that grows
Line 1 makes an empty list. List<int> is generic: the <int> fills in T, so this list holds only ints. It tracks two numbers: Count, how many values it holds, and Capacity, how many fit before it must grow. Both are 0.
Inside, a list keeps its values in an ordinary array. Add(70) finds no room, so the list makes an array of 4 slots and puts 70 in slot 0. Count is now 1 and Capacity is 4. Unused slots hold 0, the default for int.
Lines 3 to 5 add 85, 90 and 60. There is room, so each Add just writes into the next free slot: one quick step, O(1). Now Count is 4 and Capacity is 4. The array is full.
Add(95) finds the array full. An array cannot grow, so the list makes a new one twice as big, 8 slots, copies the 4 values across, then adds 95. The old array becomes garbage. Count 5, Capacity 8.
scores[1] jumps straight to slot 1 and reads 85: indexes start at 0, and the jump takes the same time however long the list is. An index of 5 or more throws ArgumentOutOfRangeException, even though Capacity is 8: Count is the limit.
Dictionary: a hash table
Line 8 makes an empty Dictionary<int, string>: each int key maps to one string value, and a key may appear only once. Inside, it is a hash table, a row of buckets. The first add makes 3 of them.
names[101] = "Ali" first asks the key for its hash code, a number. An int's hash code is the int itself. The remainder 101 % 3 = 2 picks bucket 2, and the pair 101, "Ali" goes in there.
205 % 3 = 1 and 342 % 3 = 0, so Sara and Lee land in buckets 1 and 0. Setting a key that already exists replaces its value; the Add method would throw an ArgumentException instead.
TryGetValue(205) hashes the key again: 205 % 3 = 1. It looks only in bucket 1, finds 205 and returns true with who = "Sara". No scanning, so lookups stay fast, close to O(1). A missing key returns false.
Line 13 prints 85 Sara. Choose by how you will find things: a List<T> when order and position matter, a Dictionary<TKey, TValue> when you look values up by a key.
What happens
// step 1/11 · collections List<T> values in order, read by position (index) Dictionary<TKey, TValue> values found by a key both live in System.Collections.Generic (console projects import it for you: implicit usings)
The numbers 4 and "twice as big" come from the .NET 8 source of List<T> (an empty list starts with no array, the first Add makes 4 slots, then each growth doubles). The documentation only promises that the list reallocates and copies when Count reaches Capacity, so treat the exact sizes as an implementation detail. If you know the size up front, new List<int>(1000) allocates once. The dictionary picture is simplified. In .NET 8 the table really starts with 3 buckets (a prime) and picks bucket = hash code % 3, but the pairs live in a separate entries array that the buckets point into, and keys that land in the same bucket are chained together. A 4th key here would grow the table to the next prime size (7).