# Pointers and References

> A pointer or reference is a variable holding a memory address instead of a value - the idea underneath sharing, mutation, and half the bugs that make you say 'but I didn't touch that variable.'


---

# Pointers and References

You change one variable, and a completely different one changes with it. You call a function, hand it your data, and somehow the function edits the original instead of a copy. You dereference something that turns out to be empty and the program dies on a line that looks perfectly innocent. All three of these trace back to the same idea: some variables don't hold a value - they hold *directions to where the value lives*. This guide builds that mental model once, shows you how it shows up differently across languages, and then walks through the gotchas that come from it.

## How to read this

Read it in order. Phase 1 builds the core picture - a box holding an address instead of a value - using nothing language-specific. Phase 2 shows how that one idea gets dressed up differently in different languages: explicit pointers, implicit references, and plain values that don't play this game at all. Phase 3 is the payoff: the three bugs this idea is responsible for, and why each one happens.

## The phases

1. [A box with an address instead of a value](01-a-box-with-an-address.md) - the mental model: values, variables, and what it means for a variable to point somewhere else.
2. [Pointers vs. references across languages](02-pointers-vs-references-across-languages.md) - explicit pointers, implicit references, and true value types, kept language-agnostic.
3. [The classic gotchas](03-the-classic-gotchas.md) - null dereference, dangling pointers, and the shared-reference surprise.


---

# A box with an address instead of a value

Picture a variable the way most people learn it first: a labeled box holding a value. `age` holds `30`. `name` holds `"Maria"`. You read the label, you get the value inside. That picture is correct for a lot of code, and it will actively mislead you for the rest.

Some variables don't hold a value. They hold an **address** - directions to a box somewhere else that holds the value. That's the entire idea behind a pointer or a reference: a variable whose job isn't to store data, but to say *where* the data is.

## Two boxes, not one

Say you have a chunk of data - an object, a list, a big struct - sitting somewhere in memory. Now say two different variables both need to work with it. There are two ways to set that up:

```text
Option A - copy the data
box "a" contains: [1, 2, 3]
box "b" contains: [1, 2, 3]   <- a separate, independent copy

Option B - share the data
box "data" contains: [1, 2, 3]     <- the actual list lives here, once
box "a" contains: address-of "data"
box "b" contains: address-of "data"
```

*What just happened:* in Option A, `a` and `b` are two boxes that each hold a full value. Change one, the other is untouched - they've never heard of each other. In Option B, `a` and `b` are two small boxes that each hold the same address. There is only ever *one* list. `a` and `b` are two different ways of finding it, not two different lists.

> A pointer or reference isn't a copy of the data and isn't the data itself. It's a note that says "the real thing is over there."

## Following the address is called dereferencing

Having an address isn't the same as having the value. To actually get the value, something has to go to that address and read what's there. That step - go to the address, read what's stored - is called **dereferencing**. It usually happens automatically enough that you don't notice it as a separate step:

```text
a = address-of "data"

read(a)              # dereference: go to the address, get [1, 2, 3]
a.append(4)          # dereference, then mutate what's found there
```

*What just happened:* `a` itself is small - just an address, the same size no matter how big the list is. Every operation that actually touches the list's contents has to dereference first: hop from the address to the real data, then act on it. You rarely write the word "dereference" in most languages, but the hop still happens every time.

## Why bother with this indirection at all

It looks like extra machinery for no reason, until you hit the case it solves. Copying is fine for a small number like `30`. It stops being fine when the data is a ten-thousand-row table, or when two parts of a program genuinely need to see the *same* mutable thing - a shared cache, a shared connection, a linked list node that three other nodes point to.

Pointers and references exist because copying is not always the right default:

```text
Pass a number by value  -> cheap either way, doesn't matter
Pass a 10,000-row table  -> copying it every function call would be wasteful
Two objects that must see each other's changes -> they need to share one box, not two
```

*What just happened:* an address is small and cheap to copy no matter how large the underlying data is, and sharing one address means everyone sees the same updates. That's the whole trade a pointer or reference is making - smaller, shared, but indirect instead of direct.

## The one picture to keep

Every variable is either **a box that holds a value** or **a box that holds an address pointing at another box**. Nothing else is going on. Phase 2 shows how different languages make that second kind of box explicit, invisible, or nonexistent depending on the type - but the picture underneath never changes: a value lives somewhere, and something else is pointing at where.

```quiz
[
  {
    "q": "What does a pointer or reference variable actually store?",
    "choices": [
      "A full copy of the value it refers to",
      "The address of where the real value lives",
      "A compressed version of the value",
      "Nothing until the program runs"
    ],
    "answer": 1,
    "explain": "A pointer/reference holds an address - directions to the real data, not the data itself."
  },
  {
    "q": "What does \"dereferencing\" mean?",
    "choices": [
      "Deleting a pointer",
      "Copying a pointer to a new variable",
      "Following the address to read or use the actual value stored there",
      "Converting a reference into a value type"
    ],
    "answer": 2,
    "explain": "Dereferencing is the step of going to the address and reading (or mutating) what's actually stored there."
  },
  {
    "q": "Two variables hold the same address, pointing at one shared list. If you mutate the list through one variable, what happens when you read it through the other?",
    "choices": [
      "The other variable is unaffected - it has its own copy",
      "The other variable sees the change, because there's only one underlying list",
      "The program throws an error",
      "It depends on which variable was declared first"
    ],
    "answer": 1,
    "explain": "Both variables are just two addresses pointing at the same one box. There's only one list, so both see every change."
  }
]
```

Watch it animated: [pointers and references](/explainers/Pointers.dc.html)


---

# Pointers vs. references across languages

Phase 1 built one idea: a variable can hold a value, or it can hold an address pointing at a value. Every language uses that idea - but they differ enormously in how much of it they show you, and how much they hide. Three buckets cover almost everything you'll run into: explicit pointers, implicit references, and true value types that opt out of the whole game.

## Bucket 1: explicit pointers

Some languages make the address visible in the syntax itself. C and Go are the clearest examples - you can see the moment a variable becomes an address, and the moment it gets dereferenced back into a value.

```text
x = 10
p = address-of x        // p now holds x's address, written &x in C/Go
read(p)                 // dereferencing p, written *p in C, done automatically in Go
```

*What just happened:* `&x` means "give me the address of `x`," and `*p` means "go to the address `p` holds and read what's there." Nothing here is hidden - the language gives you a symbol for "take the address" and a symbol for "follow the address." You choose, line by line, whether you're working with the value or the pointer to it.

This explicitness is also why these languages let you point at the wrong thing, or forget to check whether an address is valid before following it - more on that in Phase 3.

## Bucket 2: implicit references

Most modern languages - Python, Java, JavaScript, Ruby, C# for its object types - take the same underlying idea and remove the symbols. When you create an object and assign it to a variable, that variable is quietly holding a reference, not the object itself. There's no `&` to write and no `*` to dereference; the language does both automatically.

```text
a = new_list([1, 2, 3])   # a holds a reference to the list, though nothing says so
b = a                     # b is assigned the SAME reference, not a copy of the list
b.append(4)
read(a)                   # -> [1, 2, 3, 4] - a sees it too, same underlying list
```

*What just happened:* `b = a` looks exactly like an assignment that should make an independent copy - that's the intuition most people bring from the "box holds a value" picture. But for objects in these languages, assignment copies the *reference*, not the object. `a` and `b` are two names for one list. This is precisely the Bucket 1 idea from Phase 1, just spelled without any visible pointer syntax at all.

The practical difference from Bucket 1 isn't the underlying mechanism - it's that these languages never let you see or forge a raw address, and they generally won't let a reference point at freed memory. You get the sharing behavior without the address arithmetic.

## Bucket 3: true value types

Not everything is a reference. Plain numbers, booleans, and - in many languages - small fixed structures are **value types**: assigning or passing one really does copy the value, full stop. This is the original "box holds a value" picture from Phase 1, and it's still exactly right for these types.

```text
x = 10
y = x          # y gets its own independent copy of 10
y = y + 1
read(x)        # -> 10, completely untouched
read(y)        # -> 11
```

*What just happened:* no address, no sharing, no surprise. `x` and `y` are two separate boxes holding two separate 10s the moment the assignment happens. Languages with an explicit struct-by-value concept (C's `struct`, Go's `struct`, Rust's non-`Box` types, C#'s `struct`) extend this to bigger pieces of data too - copying the whole structure on assignment, not a reference to it.

## The table that actually matters

Forget any one language's keywords for a moment and look at the shape:

```text
Numbers, booleans, small structs-by-value  -> true copies (Bucket 3)
Objects/lists/dicts in Python, Java, JS    -> references, hidden syntax (Bucket 2)
Explicit &/* in C, Go                      -> references, visible syntax (Bucket 1)
```

*What just happened:* the mechanism underneath Bucket 1 and Bucket 2 is identical - a variable holding an address to shared data. The only real difference is whether the language shows you the address or hides it behind ordinary-looking assignment. Bucket 3 is the one genuinely different case, where the "box holds a value" picture from Phase 1 was the whole story all along.

The question worth asking about any variable in any language isn't "what syntax does this use" - it's "if I assign this to a second variable and mutate through the second one, does the first one change too?" If yes, you're holding a reference. If no, you're holding a value. Phase 3 covers what goes wrong once you're holding a reference and stop tracking that.


---

# The classic gotchas

Every bug in this guide has the same root cause: someone treated a reference like a value, or forgot that following an address only works if there's something valid at the other end. Three shapes of this cover almost every bug report that starts with "but I didn't touch that variable."

## Gotcha 1: null / nil dereference

An address variable can be empty - it points nowhere. Most languages have a specific value for this: `null`, `nil`, `None`, `nullptr`. The variable exists, but there's no address in it to follow.

```text
customer = find_customer(id)   # returns None/null if no match was found
read(customer.name)            # dereferencing None -> crash
```

*What just happened:* `find_customer` didn't find anyone, so it returned "no address" instead of an address. The next line assumes there's a customer to follow the reference to, and tries to dereference nothing. The program has no data to read, so it fails - usually with an error like `NullPointerException`, `AttributeError: 'NoneType' object has no attribute`, or a segmentation fault, depending on the language.

> A null/nil reference is a box that's plainly empty. The bug isn't the emptiness - it's dereferencing without checking first.

The fix is always the same shape: check before you follow.

```text
customer = find_customer(id)
if customer is not None:
    read(customer.name)
else:
    handle_not_found()
```

## Gotcha 2: dangling pointers and use-after-free

This one is sharper in languages with manual memory management (C, C++, and Rust if you reach for `unsafe`). An address can point at memory that used to hold valid data - and has since been freed and handed back to something else.

```text
p = allocate(SomeStruct)
free(p)              // the memory is released; p still holds the old address
read(p)               // use-after-free: reading memory that's no longer yours
```

*What just happened:* `free(p)` tells the system "I'm done with this memory, it's available again" - but it does not erase `p`. `p` still holds the same address it always did. That address is now a **dangling pointer**: it points at memory that might be reused by something completely unrelated a moment later, or might still contain the old bytes for now. Reading through it is undefined - sometimes it looks like it works, sometimes it silently corrupts unrelated data, sometimes it crashes. That inconsistency is what makes this bug miserable to track down.

```text
Null dereference    -> the address is plainly empty; fails immediately and loudly
Dangling pointer     -> the address looks valid but points at freed/reused memory; fails unpredictably
```

*What just happened:* both are "following an address that shouldn't be followed," but null dereference tends to fail fast and dangling pointers tend to fail *later*, somewhere else, in a way that looks unrelated to the actual mistake. Garbage-collected languages (Python, Java, JavaScript, Go, C#) sidestep this specific gotcha almost entirely - the runtime won't free memory that a live reference still points at. Rust prevents it at compile time in safe code through its ownership rules. It's mainly manual-memory languages where this is a live daily concern.

## Gotcha 3: the shared-reference surprise

This is the one that doesn't require any manual memory management at all - it shows up in Python, Java, JavaScript, anywhere Bucket 2 from Phase 2 applies - and it's the most common of the three in everyday application code.

```text
original = {"count": 1}
copy = original          # this looks like a copy. it is NOT.
copy["count"] = 99
read(original["count"])  # -> 99, not 1
```

*What just happened:* `copy = original` assigns the reference, not the data. `copy` and `original` are two names pointing at the exact same dictionary. Mutating through `copy` mutates the one shared object, and `original` sees it too - because there was never a second object to begin with. This is Phase 1's picture, showing up as a bug: two boxes holding the same address, and someone expected two independent boxes holding the same starting value.

The fix is to make the copy explicit when you actually want one:

```text
original = {"count": 1}
copy = clone(original)     # now an independent object with the same starting contents
copy["count"] = 99
read(original["count"])    # -> 1, untouched
```

*What just happened:* `clone` (spelled `dict(original)`, `.copy()`, `{...original}`, `Object.assign`, or similar depending on the language) allocates a genuinely new box and copies the contents into it. Now there are two addresses pointing at two different pieces of data, which is what "make a copy" actually requires.

## The one question that catches all three

Before you assign, pass, or return something that isn't a plain number or boolean, ask: *is this a reference to shared data, and does that matter here?* If it's a reference and might be empty, check before dereferencing. If it's a reference into memory you manage yourself, make sure it's still valid before you follow it. If it's a reference and you wanted an independent copy, clone it explicitly. All three gotchas are the same one instinct, applied at the point where it counts: don't assume you're holding a value when you might be holding an address.
