Collections & Arrays
Arrays, ArrayList vs generic collections, jagged arrays, dictionaries, and List internals.
8 questions
JuniorTheoryVery commonWhat is an array in C#, and what are its core runtime properties?
What is an array in C#, and what are its core runtime properties?
An array is a fixed-size, zero-indexed, homogeneous collection. Even an int[] is a reference-type object living on the heap, holding elements of one element type. Its Length is set at creation and cannot change, and every index access is bounds-checked, throwing IndexOutOfRangeException past the end.
Common mistakes
- ✗Assuming an array can grow after creation, confusing it with
List<T> - ✗Believing
int[]is a value type on the stack rather than a heap object - ✗Expecting an out-of-range index to return a default instead of throwing
Follow-up questions
- →Why is even an
int[]allocated on the heap rather than the stack? - →What does
Array.Resizeactually do under the hood?
JuniorTheoryCommonHow does a C# Array differ from ArrayList, and which should you use today?
How does a C# Array differ from ArrayList, and which should you use today?
An Array has a fixed length and is strongly typed to one element type. ArrayList is resizable but stores every item as object, so value types get boxed on insert and reads need a downcast — costing performance and type safety. Modern code prefers List<T>, which is resizable yet generic, avoiding both boxing and casts.
Common mistakes
- ✗Thinking
ArrayListis generic and type-safe likeList<T> - ✗Not realizing
ArrayListboxes value-type elements because they are stored asobject - ✗Reaching for the legacy
ArrayListin new code instead of the genericList<T>
Follow-up questions
- →What two costs does boxing in
ArrayListadd over a genericList<T>? - →Why does retrieving an item from
ArrayListrequire an explicit cast?
JuniorTheoryCommonWhat is IEnumerator, and how does foreach use it to iterate a collection?
What is IEnumerator, and how does foreach use it to iterate a collection?
IEnumerator is the cursor over a sequence: MoveNext() advances and returns whether an element exists, Current reads the element at the cursor, and Reset() rewinds. A collection implements IEnumerable, whose GetEnumerator() hands back a fresh enumerator. foreach is sugar that calls GetEnumerator(), loops on MoveNext(), and reads Current each pass.
Common mistakes
- ✗Swapping the roles of
IEnumerable(the sequence) andIEnumerator(the cursor) - ✗Thinking
foreachuses an integer index rather thanMoveNext/Current - ✗Assuming the enumerator materializes the whole sequence up front instead of lazily advancing
Follow-up questions
- →Why does modifying a collection during
foreachthrowInvalidOperationException? - →How does a
yield returniterator method generate anIEnumeratorfor you?
MiddleTheoryCommonHow does Hashtable differ from Dictionary<TKey,TValue> in C#?
How does Hashtable differ from Dictionary<TKey,TValue> in C#?
Hashtable is non-generic: its keys and values are object, so value types box on insert and reads need casts, and it is loosely typed. Dictionary<TKey,TValue> is generic and type-safe, storing keys and values as their real types with no boxing. Both hash the key for average O(1) lookup; the generic one is the modern default.
Common mistakes
- ✗Thinking
Hashtableis generic and type-safe likeDictionary<TKey,TValue> - ✗Missing that
Hashtableboxes value-type keys and values stored asobject - ✗Assuming a dictionary lookup is O(log n) tree-based rather than average O(1) hashing
Follow-up questions
- →What happens to lookup complexity when many keys hash to the same bucket?
- →Why must a key's
GetHashCodeandEqualsstay consistent for the dictionary to work?
MiddleTheoryOccasionalWhat is an anonymous type in C#, and where is it typically used?
What is an anonymous type in C#, and where is it typically used?
Writing new { Name = x, Age = y } makes the compiler emit an unnamed, immutable type with read-only properties inferred from the initializer. Since it has no name you can hold it, it is usable only through var or type inference, never declared explicitly. Its main use is shaping intermediate results in LINQ projections.
Common mistakes
- ✗Believing anonymous-type properties are mutable rather than read-only
- ✗Trying to declare or return an anonymous type by name instead of via
var - ✗Treating anonymous types as
dynamicor dictionary-backed rather than compiler-generated
Follow-up questions
- →Why can't a method return an anonymous type without using
objectordynamic? - →When do two anonymous-type expressions reuse the same generated class?
MiddleTheoryOccasionalWhat is the difference between a jagged array int[][] and a rectangular int[,]?
What is the difference between a jagged array int[][] and a rectangular int[,]?
A jagged array int[][] is an array of independent row arrays, so each row is a separate heap object and rows may differ in length. A rectangular array int[,] is a single contiguous block with fixed dimensions set at creation, where every row has the same length. Jagged trades extra indirection for ragged, per-row sizing.
Common mistakes
- ✗Believing a rectangular
int[,]allows rows of differing lengths - ✗Thinking a jagged
int[][]is one contiguous block rather than separate row objects - ✗Assuming either array's dimensions can be resized after creation
Follow-up questions
- →Why can a rectangular
int[,]have better cache locality than a jaggedint[][]? - →How do you initialize a jagged array whose rows have different lengths?
SeniorTheoryOccasionalHow does List<T> grow internally, and how do Count and Capacity differ?
How does List<T> grow internally, and how do Count and Capacity differ?
List<T> wraps a backing array. Count is how many items it holds; Capacity is how many slots that array currently has. When you Add and Count reaches Capacity, it allocates a larger array — typically doubling — and copies the existing items over. Those copies are rare and spread across many adds, so Add is amortized O(1).
Common mistakes
- ✗Confusing
Count(the item count) withCapacity(the allocated slot count) - ✗Thinking
List<T>is a linked list rather than an array-backed structure - ✗Believing every
Addreallocates, missing the amortized O(1) doubling
Follow-up questions
- →When does presetting
Capacityvia the constructor improve performance? - →Does removing items shrink
Capacity, and how would you reclaim that memory?
SeniorTheoryRareHow do Array.Clone() and CopyTo differ, and what copy depth do they give?
How do Array.Clone() and CopyTo differ, and what copy depth do they give?
Array.Clone() allocates and returns a brand-new array, while CopyTo writes elements into an existing destination array you already own. Both perform a shallow copy: value-type elements are copied bit-for-bit, but reference-type elements only have their references copied, so source and copy share the very same objects.
Common mistakes
- ✗Believing
Clone()orCopyTodeep-copies reference-type elements - ✗Expecting
Clone()to write into an existing array likeCopyTo - ✗Thinking mutating a shared object through the copy leaves the source untouched
Follow-up questions
- →How would you produce a genuine deep copy of an array of mutable objects?
- →What exception does
CopyTothrow when the destination is too small?