Arrays in memory
Why reading any element of an array takes one step, why inserting in the middle does not, and how memory layout and the CPU cache make arrays so fast.
One unbroken block
address of a[i] = start address + i × element size int[] a (4 bytes per int), starting at 0x1000:a[0] → 0x1000 a[1] → 0x1004 a[2] → 0x1008 a[3] → 0x100Ca[i] is O(1). It is called random access: you can jump to any position directly.
The price: inserting and deleting
// The array has spare room; count = how many slots are in usestatic void InsertAt(int[] a, ref int count, int index, int value){ if (count == a.Length) throw new InvalidOperationException("Array is full"); for (int i = count; i > index; i--) // shift right, starting from the end a[i] = a[i - 1]; a[index] = value; count++;} int[] data = new int[8] { 10, 20, 30, 40, 0, 0, 0, 0 };int count = 4;InsertAt(data, ref count, 1, 15);Console.WriteLine(string.Join(", ", data[..count]));10, 15, 20, 30, 40
| Operation | Cost | Why |
|---|---|---|
a[i] | O(1) | |
O(n) | ||
O(log n) | ||
O(1) | ||
O(n − i), so O(n) | ||
O(n) |
What a C# array looks like in memory
new int[1000] takes about 24 + 4,000 bytes. The length is stored in the header, which is why a.Length is O(1) and why .NET can check every index and throw IndexOutOfRangeException instead of reading random memory.
Value types ( int,double, structs like aPoint3D): the values themselves sit side by side in the block.Reference types (classes, string): the block holds 8-byte references, and the objects live somewhere else on the heap. Walking the array means jumping to a different memory location for every element.
The CPU cache: why arrays are faster than Big-O says
a[0] of an int[], a[1] to a[15] arrive in the same trip for free. Code that walks memory in order uses every byte it fetches; code that jumps around wastes most of each trip.
const int N = 4000;var grid = new double[N, N]; // C# stores rows one after another (row-major) double sum = 0; // Fast: the inner loop walks along a row, through memory in orderfor (int r = 0; r < N; r++) for (int c = 0; c < N; c++) sum += grid[r, c]; // Same Big-O, often several times slower: the inner loop jumps a whole row (32 KB) each stepfor (int c = 0; c < N; c++) for (int r = 0; r < N; r++) sum += grid[r, c];count used slots and spare room at the end. Write InsertSorted(int[] a, ref int count, int value) that puts value in the right place so the array stays sorted. Example: { 3, 8, 15, 42 } + 10 → 3, 8, 10, 15, 42.
Show a hint
value, copy it one slot right. Stop when you find a smaller one; the gap is where value goes.
Show the solution
static void InsertSorted(int[] a, ref int count, int value){ if (count == a.Length) throw new InvalidOperationException("Array is full"); int i = count - 1; while (i >= 0 && a[i] > value) { a[i + 1] = a[i]; // make room i--; } a[i + 1] = value; count++;} int[] a = new int[8] { 3, 8, 15, 42, 0, 0, 0, 0 };int n = 4;InsertSorted(a, ref n, 10);InsertSorted(a, ref n, 1);Console.WriteLine(string.Join(", ", a[..n]));1, 3, 8, 10, 15, 42
Remember
An array is one contiguous block, so a[i]is found with one calculation: O(1) random access.Inserting or deleting in the middle shifts the following elements: O(n). An array cannot grow; making it bigger means allocating a new block and copying. Walking memory in order is cache-friendly and can be many times faster than jumping around.