Visual walkthrough — Pointer to pointer
We assume only Pointers basics: a variable lives at an address, and a pointer is a variable that stores such an address. Everything else we earn.
Step 1 — A value living in a box
WHAT. We start with a single integer.
int x = 42;WHY. Before any arrows exist, we need something for the arrows to eventually reach — the treasure. Every chain of pointers, no matter how long, must terminate at real data. So we plant that data first.
PICTURE. Look at the green box below. It has two labels that matter forever:
- the name
xwritten on it, - the address
0x100— where the box physically sits in memory.
The value 42 lives inside the box; the address 0x100 is the box's location.

Step 2 — The first note: a pointer p
WHAT. We make a pointer that records where x lives.
int *p = &x; // p holds 0x100WHY. We want to reach x without knowing its name, just its location. The operator & ("address-of") reads a box's location. So &x yields 0x100, and we store that number inside a new box named p.
PICTURE. p is the blue box at address 0x200. Its value is 0x100 — the address of x. We draw that stored address as a blue arrow from p to x: the arrow literally means "the number inside me is x's location."

Notice p has its own address too: 0x200. A pointer is still just a box in memory — and that fact is the seed of the next step.
Step 3 — The * operator: walk one arrow back
WHAT. We use *p to reach the value x through the arrow.
*p // == 42WHY. p holds 0x100. To get the thing at that location, we need an operator that says "go to the address stored here, open that box." That operator is * ("dereference"). We choose * — not arithmetic, not & — because our question is literally "what lives where this arrow points?"
PICTURE. The yellow highlight below shows the walk: start at p, read its stored address 0x100, follow the blue arrow, land on x, read 42.

Step 4 — The second note: pointer to pointer pp
WHAT. Now we point at the pointer itself.
int **pp = &p; // pp holds 0x200WHY. In Step 2 we saw p is an ordinary box at 0x200. So we can take its address with & — exactly the same operator, applied one level up. &p yields 0x200. We store that in a new box pp.
Why the type int **? A C type describes how to read the bytes at the target. pp points to a box that is itself a pointer (int *). So the target's type is int *, and a pointer to that is written int ** — one star for "I am a pointer," one more for "and what I point to is also a pointer."
PICTURE. pp is the red box at 0x300. Its value is 0x200, drawn as a red arrow to p. Now we have a chain of two arrows: pp → p → x.

Step 5 — Peeling the chain: *pp then **pp
WHAT. We apply * once, then twice, and watch where each lands.
*pp // designates p (an int*, currently holding 0x100)
**pp // designates x (an int, value 42)WHY. Each * follows exactly one arrow. One dereference of pp lands on p (still a pointer — we are only halfway). To reach the treasure we must walk again, so we write *(*pp), i.e. **pp. The number of stars = number of arrows to walk.
PICTURE. Two coloured walks over the same chain: the short walk (*pp) stops on the blue box; the long walk (**pp) continues to the green box.

Step 6 — Which level you write to (the assignment side)
First, one small word we have quietly been using — let us define it before it does any work.
WHAT. The same star-count rule decides what an assignment changes.
*p = 20; // writes the box p points to → x becomes 20
**pp = 30; // two hops, lands on x → x becomes 30
*pp = NULL; // one hop, lands on p → p becomes NULLWHY. On the left of =, the stars still count hops — but now they choose which box gets overwritten. **pp walks two arrows and overwrites x. *pp walks one arrow and overwrites p, not x (because, from Step 5, *pp is the box p). This is Pass by value vs pass by reference in miniature: the level you reach is the level you can change.
PICTURE. Left panel: **pp = 30 fills the green box. Right panel: *pp = NULL blanks the blue box and snaps its arrow — x is untouched, but the chain is now broken.

Step 7 — The degenerate cases: a broken chain
WHAT. What happens if a link in the chain is NULL, or if it used to point somewhere but no longer does?
int *p = NULL;
int **pp = &p; // pp is fine: it points at a real box p
**pp; // CRASH: *pp is NULL, second hop has no targetWHY (the NULL link). pp is perfectly valid — it points at the real box p. The problem is the inner link: *pp equals p, which is NULL (the "arrow to nowhere"). The second * tries to walk an arrow that points at address 0. There is no box there, so the program dereferences invalid memory → undefined behaviour.
WHY (the dangling link). There is a sneakier failure where the inner pointer is not NULL but still bad — a dangling pointer. Suppose p pointed at memory obtained from malloc, and that memory was handed back with free(p):
int *p = malloc(sizeof(int));
int **pp = &p;
free(p); // the box p points to is gone, but p still holds its OLD address
**pp; // UNDEFINED: *pp is a valid-looking address to freed memoryHere *pp (i.e. p) still contains a real-looking address, so no NULL check catches it — yet the box it names has been reclaimed. Reading or writing **pp now touches memory you no longer own: silent corruption, not always an immediate crash. That is different from the NULL case and often harder to spot. The habit that prevents it: set p = NULL right after free(p), turning a dangling pointer back into a catchable NULL.
The lesson: pp being non-NULL is not enough. Every arrow in the chain must land on a live box before the final * is safe.
PICTURE. The red box pp still points to p, but p's arrow is a crossed-out stub that ends where a box would be if p pointed anywhere — the label NULL marks that dead end. The second walk falls off the edge.

The one-picture summary
Everything at once: three boxes, two arrows, and the star-walks labelled on the arrows themselves. & builds arrows upward (right to left); * walks arrows downward (left to right). Reading **pp = walk red arrow, then walk blue arrow, arrive at 42.

Recall Feynman retelling — the whole walk in plain words
You buried a treasure worth 42 in a box called x at spot 0x100. You wrote a note p (kept at spot 0x200) saying "treasure is at 0x100." Then you wrote a second note pp (at spot 0x300) saying "the first note is at 0x200."
Now to fetch the treasure holding only pp: read pp → it sends you to 0x200 → that's note p → read p → it sends you to 0x100 → open box → 42. Two sentences read, two walks made — that's the two stars in **pp (which always means "read pp, then read what that gave you" — inside out). One star (*pp) only gets you as far as note p itself; you're holding a note, not gold.
The & operator is writing a new note about where something is (it climbs up a level). The * operator is reading a note and walking there (it climbs down). Writing on the left side of = means you scribble over whichever box your walk lands on: two stars overwrites the treasure, one star overwrites the note p and can snap the whole chain — leaving the gold sitting there, unreachable. And if a note says "nowhere" (NULL), or points to a spot that has been bulldozed since you wrote it (a dangling pointer after free), the next walk marches into trouble.
Connections
- Pointers basics — the single arrow this whole chain is built from.
- Pass by value vs pass by reference — Step 6 is exactly why
int **reaches the caller's pointer. - Dynamic memory allocation (malloc) — where a function overwrites
*ppwith fresh memory, and where dangling pointers come from. - Pointer arithmetic — how
pp+1steps to the next pointer (arrays of pointers). - 2-D arrays vs pointer to pointer — same chains, laid out differently in memory.
- Command line arguments (argv) —
char **argv, a real two-level chain.