So far, we’ve worked with strings and arrays by directly accessing memory with simple offsets. This approach works well when data is stored sequentially. But as programs grow, we often need something more powerful — a way to store the location of data and then use that location to access or manipulate it.
This is where pointers come in. A pointer is simply a memory address stored in memory. Once you understand how to calculate and use them, you’ll unlock the ability to build dynamic data structures, lookup tables of addresses, and much more.
This lesson is entirely dedicated to understanding how pointers work — step by step.
A pointer is simply the memory address of another piece of data.
For example:
data:
DEFB 42
ptr:
DEFW data
Here:
data is stored somewhere in memory.ptr stores the address of data, not the value 42.This means ptr itself contains two bytes — the low byte and high byte of the address.
Let’s say we want to load the value stored at data using the pointer:
LD HL, ptr ; HL points to the pointer
LD E, (HL) ; Load low byte into E
INC HL
LD D, (HL) ; Load high byte into D
EX DE, HL ; HL now holds the address stored in ptr
LD A, (HL) ; A now contains 42
LD HL, ptr – HL points to the location of the pointer.LD E, (HL) / INC HL / LD D, (HL) – Load the two bytes of the pointer into DE.EX DE, HL – Swap DE and HL, so now HL contains the address the pointer was storing.LD A, (HL) – Finally, read the data from that address.This is known as dereferencing a pointer — following it to the data it points to.
Because Z80 uses 16-bit memory addresses, a pointer always requires two bytes — one for the low 8 bits and one for the high 8 bits.
For example, if data is at address 0xC000, the pointer will store:
00C0When you read the pointer, you must read both bytes and combine them to reconstruct the full address.
Pointers become most powerful when you store many of them in a table — an array of addresses.
For example:
message1: DEFB "Ready", 0
message2: DEFB "Set", 0
message3: DEFB "Go!", 0
table:
DEFW message1
DEFW message2
DEFW message3
Now table is an array of three pointers. Each entry is two bytes long.
Each pointer is two bytes, so the address of the Nth pointer is:
table + (N * 2)
For example, to load the second pointer (message2):
LD A, 1 ; Index (0-based)
ADD A, A ; Multiply by 2 (2 bytes per pointer)
LD L, A
LD H, 0
LD DE, table
ADD HL, DE ; HL = address of second pointer
LD E, (HL) ; Load low byte
INC HL
LD D, (HL) ; Load high byte
EX DE, HL ; HL = pointer to message2
LD A, (HL) ; Load first byte of message2
Let’s break this down carefully:
HL to the correct pointer’s location.HL and use it.This four-step pattern is the backbone of how games and operating systems store and retrieve structured data.
ArraysPointersStore data directlyStore addresses of dataAccess by base + offsetAccess by base + (index × 2), then followFixed structureFlexible, data can be anywhereNo indirectionRequires dereferencing
Arrays are simple and fast, but pointers are far more flexible — they allow you to rearrange data, store variable-length strings, and build complex structures without hardcoding addresses.
A by following the pointer.(base + index × 2) is essential — it’s the key to advanced memory access patterns.