ArraysLesson 1

How Arrays Work

Contiguous blocks of memory with instant access by index. How they are stored, why random access is O(1), and their limitations.

What Is It?

An array is the simplest data structure. It stores items one after another in memory, all packed together with no gaps. This side-by-side arrangement has a name: contiguous storage.

That one property — items stored right next to each other — gives arrays their superpower: you can jump to any item instantly, by index.

How? Because each slot is the same size, and they all start at a known position. Finding item number 3 is just math: start at the beginning, skip 3 slots forward, done. No searching. No guessing.

This lesson explains how that works under the hood, and why it matters for writing fast code.

Analogy

The Parking Lot

Imagine a parking lot with numbered spaces: Space 0, Space 1, Space 2, and so on. Every space is exactly the same size, laid out in one long row.

If someone tells you "your car is in space 47", you don't walk past every space checking labels. You know each space is 10 feet wide, so you walk directly to the 470-foot mark. That's random access — you calculate the position with math, not searching.

Now imagine the lot is completely full and someone needs a new spot. There's no room — you'd have to find a bigger lot and move every car. That's the fixed-size problem of arrays.

And if someone needs to squeeze into space 3 (not at the end), every car in spaces 3, 4, 5, ... has to shift one spot. That's why inserting in the middle of an array is slow.

Try It Yourself

How It Works

Memory Layout of an Array

When you create an array of 5 integers, here's what happens in memory:

Array in Memory5 integers × 4 bytes = 20 bytes at address 1000
1000
1004
1008
1012
1016
42
17
83
5
61
index 0
index 1
index 2
index 3
index 4

Base address: 1000 · Element size: 4 bytes · Total: 20 bytes

Each integer takes 4 bytes. Five integers take 20 bytes total. They sit right next to each other, starting at address 1000.

The Address Formula

This simple formula is the key to why arrays are fast:

pseudocode
1
address of array[i] = base_address + (i × element_size)

Let's trace it for our example (base address 1000, element size 4 bytes):

array[0]
42
array[1]
17
array[2]
83
array[3]
5
array[4]
61

This works because:

  1. Every element is the same size (4 bytes for integers)
  2. Elements are stored right next to each other (no gaps)
  3. We know where the array starts (the base address)

It doesn't matter if you want index 0 or index 99,999 — the math takes the same amount of time. This is what O(1) means: always fast, no matter the size.

Fixed Size — No Growing Allowed

When you declare an array of 5 slots, the system reserves exactly 20 bytes. Not 21. You get 5 slots and that's it.

Fixed-Size Arrayexactly 5 slots — no more
[0]
[1]
[2]
[3]
[4]

Address 1020 belongs to something else — writing there is a buffer overflow

Writing to array[5] would touch memory that doesn't belong to you. This is called a buffer overflow — a serious bug that can crash programs or corrupt data.

If you need a 6th element, you have to:

  1. Allocate a new, larger array
  2. Copy all 5 elements into it
  3. Add the 6th element
  4. Throw away the old array

Most languages hide this behind dynamic arrays (covered in the next lesson), which do this automatically.

Bounds Checking — Staying Safe

Using an index that's too large (or negative) is called an out-of-bounds access. Different languages handle it differently:

array[5]
OUT OF BOUNDS
array[-1]
OUT OF BOUNDS
array[100]
OUT OF BOUNDS

Some languages check every access and throw an error immediately — safer, but a tiny bit slower. Others don't check and silently read whatever memory is at that calculated address — faster, but dangerous and a common source of bugs.

Here's what safe bounds checking looks like:

pseudocode
1
2
3
4
function get(array, index):
if index < 0 or index >= array.length:
error "Index out of bounds"
return array[index]

Why Looping Through Arrays Is Fast

Because all the elements are stored next to each other, your computer can grab several of them in a single trip to memory. When you access the first element, it quietly loads the neighboring elements too. By the time your loop reaches them, they're already nearby and ready — no extra waiting.

Cache Line (64 bytes)one fetch loads 16 integers into fast cache
a0
a1
a2
a3
a4
a5
a6
a7
a8
a9
a10
a11
a12
a13
a14
a15

Accessing a1–a15 after loading a0: ~1ns each (cache hit) vs ~100ns from RAM

This is why a simple loop over an array is one of the fastest things you can do in a program. Contrast this with jumping around to random positions — that's slower because the computer can't predict what to load ahead of time.

Multi-Dimensional Arrays

A 2D array (like a grid or matrix) is still just one flat block of memory. A 3×3 grid is stored as 9 elements in a row:

Matrix (logical view)

1
2
3
4
5
6
7
8
9

3 × 3 grid

Memory (physical layout)

1
2
3
4
5
6
7
8
9

address = base + (row × cols + col) × size

9 contiguous elements

The formula to find element at row r, column c:

pseudocode
1
address = base + (r × num_cols + c) × element_size

Examples

Example 1: Declaring and accessing

pseudocode
1
2
3
4
5
6
// Create an array of 5 integers (stored starting at address 2000)
array = [10, 20, 30, 40, 50]
// Access element at index 3
address = 2000 + (3 × 4) = 2012
value at 2012 = 40

Example 2: Byte-level view

Storing three integers — 255, 256, 0 — in memory:

Byte-Level Viewarray [255, 256, 0] as 32-bit integers
500
501
502
503
504
505
506
507
508
509
510
511
0xFF
0x00
0x00
0x00
0x00
0x01
0x00
0x00
0x00
0x00
0x00
0x00
255
↑ [0]
256
↑ [1]
0
↑ [2]

Each integer = 4 bytes. 255 fits in one byte (0xFF), 256 needs two (little-endian)

Each integer uses 4 bytes, no matter the value. Even the number 0 occupies 4 bytes.

Example 3: Out-of-bounds danger

pseudocode
1
2
3
4
5
6
7
8
9
array = [10, 20, 30] // 3 elements starting at address 800
// Some other variable sitting right after our array:
other_var = 999 // happens to be stored at address 812
// Without bounds checking:
array[3] = 0 // calculates: 800 + (3 × 4) = 812
// This OVERWRITES other_var!
// The program doesn't crash — it silently corrupts data.

Common Mistakes

  1. Off-by-one errors. Arrays are zero-indexed. An array of size 5 has valid indices 0 through 4. Accessing array[5] is out of bounds. This is the single most common array bug.
  1. Assuming arrays can grow. A fixed-size array cannot accommodate more elements than its declared size. Writing past the end corrupts other data or crashes the program.
  1. Confusing size with last valid index. If length = 5, the last valid index is 4 (which is length - 1). Using length as an index is always out of bounds.
  1. Thinking access time varies by index. Accessing array[0] and array[99999] take exactly the same time. The address formula is equally fast for any index — that's the whole point.

Best Practices

  • Always check that your index is valid before using it — either manually or by relying on your language's built-in bounds checking.
  • Use arrays when you need fast access by position and know roughly how many elements you'll have.
  • When in doubt, use your language's built-in list or dynamic array type — it handles sizing for you.
  • Iterate through arrays from start to end whenever possible — this is the most predictable and efficient pattern.