<style> .reveal-up, .reveal-left, .reveal-right, .reveal-stagger > * { opacity: 1 !important; transform: none !important; } </style>
  1. Learn
  2. DSA
  3. Linear data structures
All lessons in this course

Lesson 6 of 50 13 minBeginner

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.

The array is the most important data structure of all. Almost every other structure (lists, stacks, queues, hash tables, heaps) is built on top of one. To use them well you need to know what an array really is in memory.

One unbroken block

An array is a single contiguous block of memory: element 0, then element 1 directly after it, then element 2, with no gaps. Every element has the same size, so the computer can work out where any element lives with one small calculation:

Address formula
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] → 0x100C

One multiplication and one addition, whether the index is 3 or 3 million. That is why reading or writing a[i] is O(1). It is called random access: you can jump to any position directly.

Interactive

An array is one block of memory

Click a slot to see how its address is calculated. Then insert or delete in the middle and count how many elements must move.

Element type
address of a[3] = start + 3 × 4 = 0x1000 + 12 = 0x100C · one multiplication, whatever the index: O(1)
Five ints in a block with room for eight. Every slot sits right next to the previous one.

The price: inserting and deleting

Because there are no gaps, putting a value between two elements means moving every element after it one slot to the right. Removing one means moving them all left to close the hole. The cost depends on the position: free at the end, n moves at the front.

InsertAt.cs
// 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]));
Output
10, 15, 20, 30, 40
OperationCostWhy
Read / write a[i]O(1)Address formula
Search an unsorted arrayO(n)Must check each element
Search a sorted arrayO(log n)Binary search (next module)
Insert / delete at the end (with spare room)O(1)Nothing moves
Insert / delete at index iO(n − i), so O(n)Elements after i shift
Grow the arrayO(n)Impossible in place: allocate a bigger block and copy

What a C# array looks like in memory

In .NET an array is an object on the heap. It starts with a small header (about 24 bytes on 64-bit: type information, a sync block and the length), followed by the elements. So 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.

What the elements contain depends on the type:

  • Value types (int, double, structs like a Point3D): 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

Reading from main memory is slow (around 100 ns). The CPU therefore loads memory in cache lines of 64 bytes and keeps them in a small, very fast cache. When you read 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.

CacheOrder.cs
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];
Quick check

An int[] starts at address 0x2000. What is the address of a[5]?

Quick check

An array holds 1,000 elements with room for more. Which operation moves the most elements?

Practice

A sorted array has 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

Start from the last used element and walk left. While the element is bigger than value, copy it one slot right. Stop when you find a smaller one; the gap is where value goes.

Show the solution
Solution.cs
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]));
Output
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.
0 of 50 lessons completed