TRT
Sign in

Foundations · Module 1 · Lesson 2 of 4

Memory addresses and pointers

Memory is one long row of numbered bytes. A pointer is a variable that holds one of those numbers, and following it is how every linked structure works.

FreeAbout 12 minutes

By the end you can

  • Describe memory as numbered bytes, and explain what an address is
  • Trace what a pointer stores and what following it does
  • Tell copying a value from copying a reference, in C and in higher-level languages

Builds on Bits, bytes and number representation.

01 · A question first

In Python, after b = a and b.append(3), the list a has changed too. After b = a and b += 1 on an integer, a has not. What was actually copied in each case?

Make a guess, then step through the trace below to check it.

02 · Watch it run

A pointer, followed and movedstep 1 of 6
long x = 42;     // word 0, address 0x1000long *p = &x;    // word 1, address 0x1008*p = 7;long y = x;      // word 2, address 0x1010p = &y;*p = 9;
  1. word 042(looked at in this step)
  2. word 1empty
  3. word 2empty
  4. word 3empty

x is an 8-byte variable. The compiler placed it at address 0x1000, shown here as word 0, and stored 42 there.

Machine work: one 8-byte store to 0x1000

03 · The idea

Memory is a numbered row of bytes

To a running program, memory looks like one enormous array of bytes. Every byte has a number, its address, starting at 0 and counting up. On a 64-bit machine addresses are 64-bit numbers, written in hexadecimal such as 0x7ffd5a10. Reading memory means "give me the bytes starting at this address"; writing means "put these bytes there".

Each process gets its own private numbering, called virtual memory. The operating system and the processor translate those addresses to physical memory behind the scenes, so two programs can both use address 0x1000 without meeting. From inside a program, though, the model is simple: a row of numbered bytes.

Variables have addresses

When you declare a variable, the compiler or runtime reserves enough bytes for it somewhere in that row, and the variable's name becomes shorthand for that location. In the trace above, x is an 8-byte long, so it occupies the 8 bytes starting at 0x1000. Writing x = 42 stores 42 in those bytes; reading x loads them.

Different variables live at different addresses, and the address of a variable doesn't change while it exists. That is what makes the next idea possible.

A pointer holds an address

A pointer is a variable whose value is an address. In C, &x produces the address of x, and long *p = &x stores it in p. The trace shows the result: p lives at 0x1008, and the number stored there is 0x1000. p doesn't contain 42. It contains the location of 42.

Following a pointer is called dereferencing, written *p in C. The processor first loads p to learn the address, then reads or writes at that address. So *p = 7 changes x, even though the line never names x. That indirection is the whole point: code can work on a value without knowing in advance which variable holds it.

A pointer is just a number, so it takes 8 bytes on a 64-bit machine whatever it points to. A pointer can also point nowhere. The convention is address 0, called null, which no valid object ever uses. Dereferencing null crashes a C program with a segmentation fault, and in Java it throws a NullPointerException.

Copying a value or copying a reference

The trace ends with two kinds of copy. long y = x copies the value: y gets its own 7 in its own bytes, and later changes to y never reach x. p = &y copies an address: p now leads to y, and writing through it changes y.

Higher-level languages hide the & and *, but the same two kinds of copy are everywhere:

  • In Python, every variable is a reference to an object. b = a copies the reference, so both names lead to the same list, and b.append(3) changes the list both of them see. Integers are references too, but they can't be changed in place: b += 1 creates a new integer object and points b at it, leaving a alone. That is the answer to this lesson's opening question.
  • In Java and C#, variables of primitive types such as int and double hold values, and variables of class types hold references. Assigning an array or an object copies the reference.
  • In JavaScript, objects and arrays are handled by reference, and numbers and strings behave like values.
  • Go and C++ have explicit pointers and also let you copy whole structs by value.

When two names lead to the same object, the object is aliased. Aliasing is efficient, because a reference costs 8 bytes however large the object is. It is also a common source of bugs: a function that "only reads" a list it was given can still change it, and a default argument in Python that is a list is shared by every call. When you need an independent object, make a copy explicitly, and decide whether a shallow copy (the outer object only) or a deep copy (everything it refers to) is what you need.

When pointers go wrong

A pointer is only meaningful while the memory it points to still holds what you think it holds. Three classic failures come from breaking that:

  1. Dangling pointers. A local variable lives in the function's stack frame, which is released when the function returns. A pointer to it still holds the old address, but the next function call reuses that memory. Freeing heap memory and then using a pointer to it causes the same problem, called use after free.
  2. Leaks. Memory allocated on the heap and never freed stays allocated until the program exits. A long-running server that leaks a little per request eventually runs out of memory.
  3. Null dereferences. Following a pointer that holds 0.

C and C++ leave all three to the programmer. Microsoft and the Chromium project have each reported that about 70% of their serious security bugs are memory-safety bugs of this kind. Garbage-collected languages such as Java, Python and Go rule out dangling pointers and most leaks by freeing an object only when nothing refers to it any more. Rust rules them out at compile time instead.

Why data structures are made of pointers

An array can be found with one address and some arithmetic, as the next lesson shows. Most other structures in this track can't. A linked list node holds a pointer to the next node; a tree node holds pointers to its children; a graph stores, for each node, pointers or indexes of its neighbors. Pointers are what let these structures grow one piece at a time and be rearranged by changing a few addresses instead of moving data.

They also have a cost that the trace hints at: *p = 7 took two memory accesses, and the second couldn't start until the first finished. The next two lessons show why that matters so much on real hardware.

Pseudocode & Multi-Language Implementations (C++, Java, Python)

To translate Memory addresses and pointers from mental model into production and interview code, start from the language-independent pseudocode below and compare how C++, Java, and Python express the exact same loop invariant:

text
long x = 42;     // word 0, address 0x1000
long *p = &x;    // word 1, address 0x1008
*p = 7;
long y = x;      // word 2, address 0x1010
p = &y;
*p = 9;
cpp
#include <vector>
#include <algorithm>
 
long long processArrayInvariants(std::vector<int>& nums) {
    long long acc = 0;
    int lo = 0, hi = static_cast<int>(nums.size()) - 1;
    while (lo <= hi) {
        acc += nums[lo];
        if (lo < hi) acc += nums[hi];
        ++lo; --hi;
    }
    return acc;
}
java
public class ArrayInvariantSolver {
    public static long processArrayInvariants(int[] nums) {
        long acc = 0;
        int lo = 0, hi = nums.length - 1;
        while (lo <= hi) {
            acc += nums[lo];
            if (lo < hi) acc += nums[hi];
            lo++; hi--;
        }
        return acc;
    }
}
python
def process_array_invariants(nums: list[int]) -> int:
    acc = 0
    lo, hi = 0, len(nums) - 1
    while lo <= hi:
        acc += nums[lo]
        if lo < hi:
            acc += nums[hi]
        lo += 1
        hi -= 1
    return acc

Complexity Derivation, Edge Cases & Common Mistakes

  • Time & Space Complexity Derivation: Always count how many times the inner state advances relative to input size n (amortized pointer movements, tree height O(log n), or hash-table load factor < 1) and distinguish auxiliary space (extra stack frames or buffers allocated by your algorithm) from the input storage itself.
  • Boundary & Edge Cases to Verify: Test empty or single-element inputs (n <= 1), all-duplicate values, already-sorted and reverse-sorted sequences, disconnected components, and 32-bit signed integer limits (2^31 - 1) where intermediate sums or index products require 64-bit long long / long.
  • Common Beginner Mistakes: Avoid recomputing prefix/window state from scratch inside nested loops, mutating a container while iterating over it, or forgetting base-case termination guards before dereferencing pointers or indices.

04 · Prove it

Question 1 of 4 · answer from memory

p holds 0x1000, and x lives at address 0x1000. After *p = 7, what is x?

05 · Practice

Something wrong with this lesson? Report a problem

Not started

Sign in to save your learning progress.

Sign in to save