C# & .NET Framework Arrays & String Mastery
💡
Exercise 30

Arrays & Strings — Mastery Quiz 20 XP Medium

Ctrl+Enter Run Ctrl+S Save

🧠 Arrays & Strings — Interview Gauntlet

You've mastered the material warehouse, mapped city grids, operated the label maker, and built reports at lightning speed. Now the .NET Architect faces the ultimate test: 15 deeply technical interview questions covering arrays, strings, and the hidden mechanics of the CLR.

These questions go beyond syntax — they probe your understanding of memory layout, performance characteristics, type safety, and modern .NET APIs. Think carefully before answering. The answers may surprise even experienced developers.

📋 Instructions
🧠 Quiz Time
0 / 15 answered
1
What is the key difference between `Array` (System.Array), `ArrayList`, and `List<T>` in .NET?
A. Array is dynamic, ArrayList is fixed-size, List<T> is generic
B. Array is fixed-size and type-safe, ArrayList is dynamic but stores objects (boxing for value types), List<T> is dynamic and generic (no boxing)
C. All three are identical in performance and behavior
D. ArrayList is generic, List<T> is non-generic, Array is abstract
Array has a fixed size set at creation and is type-safe. ArrayList is dynamically sized but stores everything as `object`, causing boxing for value types and losing type safety. List<T> combines the best of both: dynamic sizing with generic type safety and no boxing. ArrayList is largely obsolete since .NET 2.0 introduced generics.
2
Why are strings immutable in .NET, and what is the consequence of modifying a string?
A. Strings are mutable — modifications happen in-place for efficiency
B. Strings are immutable for thread safety and interning; every modification creates a new string object on the managed heap
C. Strings are immutable only when declared as `const`; regular string variables are mutable
D. Strings are stored on the stack and cannot be resized, making them immutable
String immutability enables safe sharing across threads without locks, allows the runtime to intern (deduplicate) identical literals, and makes strings usable as reliable dictionary keys. Every operation like `Replace()`, `ToUpper()`, or `+` creates a brand new `System.String` object, leaving the original unchanged. This is why StringBuilder exists for scenarios with many modifications.
3
What is the time complexity of concatenating `n` strings in a loop using `+=` versus using `StringBuilder.Append()`?
A. Both are O(n)
B. String += is O(n²); StringBuilder.Append is O(n) amortized
C. String += is O(n log n); StringBuilder.Append is O(1)
D. String += is O(n); StringBuilder.Append is O(n²)
Each `+=` creates a new string and copies all previous characters, so for n iterations of growing strings, you copy roughly 1 + 2 + 3 + ... + n = O(n²) total characters. StringBuilder maintains a mutable char buffer that grows by doubling, so each Append is O(1) amortized, giving O(n) total for n appends. This difference becomes dramatic with large n.
4
How do 2D rectangular arrays (`int[,]`) and jagged arrays (`int[][]`) differ in memory layout?
A. Both are stored identically as a single contiguous block of memory
B. Rectangular arrays are a single contiguous block; jagged arrays are an array of references pointing to separate inner arrays on the heap
C. Jagged arrays are contiguous; rectangular arrays use pointers to separate rows
D. Rectangular arrays are stored on the stack; jagged arrays are stored on the heap
A rectangular `int[3,4]` allocates one contiguous block of 12 ints. A jagged `int[3][]` allocates an outer array of 3 references, each pointing to a separately allocated inner array. This means jagged arrays can have rows of different lengths, but they involve more pointer indirection and more heap allocations. The JIT can sometimes optimize jagged array access better because each inner array has its own bounds check that can be hoisted out of inner loops.
5
What is array covariance in C#, and why is it considered a design flaw?
A. Array covariance allows value-type arrays to be cast to object arrays safely
B. Array covariance means `string[]` can be assigned to `object[]`, but writing an incompatible type causes a runtime `ArrayTypeMismatchException` instead of a compile-time error
C. Array covariance prevents any base-type array assignment and is a compile-time restriction
D. Array covariance only applies to jagged arrays, not to single-dimensional arrays
C# allows `object[] arr = new string[5];` — this compiles fine because `string` derives from `object`. But if you then do `arr[0] = 42;` (an int), it compiles successfully but throws `ArrayTypeMismatchException` at runtime. This breaks type safety by deferring a detectable error to runtime. It exists for Java compatibility reasons. The CLR must perform a type check on every array element write, adding overhead. Generic collections like `List<T>` avoid this problem entirely.
6
What are `Span<T>` and `Memory<T>`, and how do they improve array/string processing?
A. They are obsolete types replaced by ArraySegment<T>
B. Span<T> is a stack-only ref struct providing a type-safe view over contiguous memory (arrays, stackalloc, native) without allocations; Memory<T> is a heap-safe equivalent that can be stored in fields and used with async methods
C. Span<T> creates a copy of the array for safe mutation; Memory<T> is the read-only version
D. Both are only available in .NET Framework 4.8 and later
Span<T> is a `ref struct` that can point to a slice of an array, a stackalloc buffer, or unmanaged memory — all without allocating a new array. It enables zero-copy slicing: `Span<int> slice = array.AsSpan(2, 5)` gives you a view of 5 elements starting at index 2 with zero heap allocations. Being a ref struct, it can only live on the stack (no fields, no async, no boxing). Memory<T> removes these restrictions at the cost of slightly less optimization potential.
7
What is the difference between `String.Empty`, `""`, and `null` for a string variable?
A. All three are identical and interchangeable in all contexts
B. `String.Empty` and `""` both reference the same interned empty string object (length 0); `null` means no object reference at all — calling methods on it throws NullReferenceException
C. `String.Empty` allocates a new empty string each time; `""` is interned; `null` is the same as `""`
D. `null` is an empty string with length 0; `String.Empty` is a compile-time constant
`String.Empty` is a static readonly field equal to `""`. Both refer to the same interned string object with length 0. They are valid string references, so `"".Length` returns 0. `null` is the absence of any object — calling `.Length` or any method on null throws `NullReferenceException`. Use `string.IsNullOrEmpty()` to check for both. `String.Empty` is preferred by some style guides for clarity, but `""` achieves the same result.
8
What is the purpose of the `StringComparison` enum, and why should you specify it explicitly?
A. It controls string encoding (UTF-8, UTF-16, ASCII)
B. It specifies the comparison rules (ordinal, culture-sensitive, case-insensitive) for methods like `Equals()`, `Compare()`, and `IndexOf()`, avoiding subtle culture-dependent bugs and improving performance with Ordinal comparisons
C. It is only used for sorting strings in arrays and has no effect on equality checks
D. It determines whether strings are stored on the stack or heap
`StringComparison` has six values: `Ordinal`, `OrdinalIgnoreCase`, `CurrentCulture`, `CurrentCultureIgnoreCase`, `InvariantCulture`, and `InvariantCultureIgnoreCase`. Ordinal comparison compares raw UTF-16 code units — it's the fastest and most predictable. Culture-sensitive comparisons follow linguistic rules (e.g., in Turkish, 'i'.ToUpper() is 'İ', not 'I'). Always specify `StringComparison` to be explicit about your intent and avoid the "Turkish-I problem" and other locale-dependent surprises.
9
How does the CLR handle array bounds checking, and can it be eliminated?
A. The CLR performs no bounds checking; out-of-range access causes undefined behavior like in C/C++
B. The CLR inserts a bounds check on every array access, but the JIT compiler can eliminate redundant checks when it can prove the index is within bounds (e.g., standard for-loop patterns)
C. Bounds checking only occurs in Debug builds and is completely removed in Release builds
D. Bounds checking is performed by the garbage collector, not the JIT
The CLR mandates bounds checking on every array access to maintain memory safety — accessing index -1 or array.Length throws IndexOutOfRangeException instead of corrupting memory. However, the JIT compiler performs "bounds check elimination" as an optimization: in a canonical `for (int i = 0; i < arr.Length; i++)` loop, it can prove `i` is always in-bounds and remove the per-access check. Non-standard loop patterns may not get this optimization.
10
How does the `params` keyword interact with arrays in C# method signatures?
A. `params` creates a List<T> internally, not an array
B. `params` allows a method to accept a variable number of arguments that the compiler packages into an array; it must be the last parameter and can only appear once
C. `params` makes the array parameter optional and defaults it to null
D. `params` creates a Span<T> for zero-allocation variable arguments
Declaring `void Log(params string[] messages)` lets callers write `Log("a", "b", "c")` instead of `Log(new string[] {"a", "b", "c"})`. The compiler allocates a new array behind the scenes. It must be the last parameter. A caller can also pass an existing array directly. Note: In .NET 8+ / C# 12, `params` can also work with `Span<T>` and other collection types, enabling stack allocation to avoid the heap array.
11
What is `ReadOnlySpan<char>` and how does it relate to string processing?
A. It is a mutable view over a string's internal char buffer
B. It is a read-only, stack-only view over contiguous memory that enables zero-allocation string slicing — `string.AsSpan()` returns a ReadOnlySpan<char> without copying
C. It is a thread-safe wrapper around StringBuilder
D. It is only available in unsafe code blocks
Since strings are immutable sequences of chars, `ReadOnlySpan<char>` is a natural fit for slicing them without allocation. `"Hello World".AsSpan(6, 5)` gives you a ReadOnlySpan<char> pointing to 'World' — no new string object, no heap allocation, no copy. Methods like `int.Parse(ReadOnlySpan<char>)` accept spans directly, enabling high-performance parsing pipelines that avoid intermediate string allocations entirely.
12
What does `String.Intern()` do, and when is it useful?
A. It compresses a string to use less memory
B. It adds a string to the CLR's intern pool so that future identical strings share the same reference, enabling fast reference-equality checks with `Object.ReferenceEquals`
C. It converts a string to a byte array for serialization
D. It makes a string mutable for in-place editing
The CLR automatically interns all string literals at compile time. `String.Intern(str)` manually adds a runtime-created string to the intern pool, returning the interned reference. After interning, you can use `Object.ReferenceEquals(a, b)` for O(1) equality checks instead of O(n) character-by-character comparison. This is useful when you have many duplicate strings (e.g., parsing XML tags or CSV headers). Caution: interned strings are never garbage collected, so interning unbounded unique strings causes a memory leak.
13
What is the difference between `Buffer.BlockCopy` and `Array.Copy`?
A. They are identical methods in different namespaces
B. `Buffer.BlockCopy` copies raw bytes between primitive-type arrays (fast, no type conversion); `Array.Copy` copies elements with type checking, boxing/unboxing support, and widening conversions
C. `Array.Copy` only works with string arrays; `Buffer.BlockCopy` works with all types
D. `Buffer.BlockCopy` is managed; `Array.Copy` is unmanaged
`Buffer.BlockCopy(src, srcOffset, dst, dstOffset, count)` treats arrays as raw byte buffers — offsets and count are in bytes, not elements. It works only on primitive types (int, float, byte, etc.) and performs a direct memory copy (like C's memcpy). `Array.Copy` is element-aware: it understands types, performs necessary conversions, handles reference types, and works with any array type. Use BlockCopy for maximum performance with primitive arrays when you know the byte layout.
14
What does `stackalloc` do when creating arrays, and when should you use it?
A. It allocates the array on the managed heap with priority garbage collection
B. It allocates a block of memory on the stack instead of the heap, avoiding GC pressure; useful for small, short-lived buffers in performance-critical code
C. It creates a global static array shared across all threads
D. It is a deprecated keyword replaced by `Span<T>`
`Span<int> buffer = stackalloc int[256];` allocates 256 ints (1 KB) on the stack frame. When the method returns, the memory is reclaimed instantly — no GC involved. This is ideal for small temporary buffers in hot paths (parsing, cryptography, encoding). Dangers: the stack is limited (~1 MB by default), so large allocations cause StackOverflowException. Combined with Span<T>, stackalloc is safe and easy to use without `unsafe` blocks (C# 7.2+).
15
What is `ImmutableArray<T>` and how does it differ from a regular array and `ReadOnlyCollection<T>`?
A. It is a resizable array that prevents element modification
B. It is a truly immutable, struct-based array wrapper (System.Collections.Immutable) that guarantees no mutation, uses structural sharing for efficient copies, and is safe to share across threads without locks — unlike ReadOnlyCollection<T> which only wraps a mutable list
C. It is the same as `readonly int[]` — prevents reassignment but not element mutation
D. It is only available in F# and cannot be used from C#
`ImmutableArray<T>` (from the System.Collections.Immutable NuGet package) is a struct that wraps an array and exposes no mutation methods. `readonly int[]` only prevents field reassignment — you can still do `arr[0] = 42`. `ReadOnlyCollection<T>` wraps a mutable list and blocks mutation through its own API, but someone with a reference to the original list can still modify it. ImmutableArray truly guarantees immutability. Its builder pattern (`ImmutableArray.CreateBuilder<T>`) allows efficient construction before freezing.
main.py
Hi! I'm Rex 👋
Output
Ready. Press ▶ Run or Ctrl+Enter.