A pointer is a value that holds the location of another value, rather than the value itself. That difference explains why changing one variable can change another, and why some programs crash with a null pointer error.
What a pointer stores
Every value your program uses lives somewhere in memory, and every spot in memory has an address: a number, usually written in hexadecimal like 0x7ffd5e8c. A normal variable holds a value. A pointer is a variable that holds an address.
Think of a small slice of memory like this:
| Address | Name | Holds |
|---|---|---|
0x1000 | tuna | 42 |
0x2000 | note | 0x1000 |
tuna holds the number 42. note holds 0x1000, which is where tuna lives. note only says where to find tuna.
Making and following a pointer in C
C shows pointers more plainly than most languages, so it is the clearest place to see them. Two operators do the work:
&gives you the address of a variable ('where does this live?').*follows a pointer to the value at that address. This is called dereferencing.
#include <stdio.h>
int main(void) {
int tuna = 42;
int *note = &tuna; // note holds tuna's address
printf("%d\n", *note); // prints 42
*note = 7; // change the value it points to
printf("%d\n", tuna); // prints 7
return 0;
}int *note declares a pointer to an int. Writing *note = 7 follows the address and changes what is stored there, while note keeps the same address. That is why tuna reads 7 afterwards. There was only ever one 42, and both names lead to it.
Why pointers are useful
Sharing without copying
Copying a single number is cheap. Copying a 10 MB image, or a list of a million users, every time you hand it to a function is not. With a pointer, you pass an address, which is the same small size whatever it points to. Everyone who receives the address works on the same data, and nothing is copied.
Letting a function change the caller's data
In C, a function gets copies of its arguments. If you want a function to change a variable you own, you give it the variable's address instead:
void refill(int *bowl) {
*bowl = 100;
}
int main(void) {
int tuna = 0;
refill(&tuna);
// tuna is now 100
return 0;
}Without the pointer, refill would set its own copy to 100 and the caller's tuna would stay at 0.
Building linked structures
Linked lists, trees and graphs are made of nodes that point to other nodes. Each node stores its own data plus the addresses of its neighbours or children. That is how a tree can grow one node at a time without moving everything already in it.
Pointers in languages that hide them
Python, JavaScript and Java don't give you raw addresses to work with, but they use the same idea everywhere. Their variables for objects hold references: managed pointers that the language follows for you.
bowl = ["tuna"]
note = bowl # copies the reference, not the list
note.append("salmon")
print(bowl) # ['tuna', 'salmon']note = bowl doesn't make a second list. It makes a second name for the same list, so a change through one name shows up through the other. If you want an independent list, ask for one with bowl.copy(). It is the same idea as the C example: two names, one value.
When a pointer goes wrong
A pointer is only useful while the thing it points to is still there. There are two classic ways for that to go wrong.
Null pointers: pointing at nothing
Most languages have a special 'points to nothing' value: NULL in C, null in Java and JavaScript, None in Python. It is handy for saying 'there is no value here yet'. The trouble starts when code follows it anyway.
String tuna = null;
int size = tuna.length(); // NullPointerExceptionJava throws a NullPointerException, Python raises an AttributeError about NoneType, and JavaScript throws a TypeError. In C, dereferencing NULL is undefined behaviour, which on most systems ends in a crash such as a segmentation fault.
Dangling pointers: pointing at freed memory
In C and C++ you can free memory yourself. If a pointer still holds the address after the memory is freed, it is a dangling pointer: it looks valid, but what it points to has been thrown away.
#include <stdlib.h>
int *note = malloc(sizeof *note);
*note = 42;
free(note);
// note still holds the old address
*note = 7; // use after free: undefined behaviourThis is worse than a null pointer, because nothing guarantees a crash. The program might crash, read rubbish, or quietly overwrite data that now belongs to something else. 'Use after free' bugs like this are a well-known source of security holes. A common habit is to set the pointer to NULL straight after freeing it, so any later mistake fails loudly instead of silently.
Languages with a garbage collector, such as Java, Python and JavaScript, avoid dangling pointers by never freeing an object while anything still refers to it. Null references are still possible there, which is why the null pointer error is the one most developers meet first.
How modern languages make pointers safer
Language designers have added guard rails against these bugs:
- Garbage collection removes manual freeing, and with it most dangling pointers. The cost is some runtime overhead and less control over when memory is released.
- Smart pointers in C++ (
std::unique_ptr,std::shared_ptr) free memory automatically when the last owner is done with it. - Rust's borrow checker checks at compile time that a reference can never outlive the value it refers to, so code with a dangling reference doesn't compile.
- Null-safe types in Kotlin, Swift, C# (nullable reference types) and TypeScript (with strict null checks) make 'this might be null' part of the type, so the compiler makes you handle it before you dereference.
None of these remove the core idea. They all still pass around 'where the thing is' rather than the thing itself.
Common mistakes
- Using a pointer before giving it an address. An uninitialised pointer in C holds an unpredictable address, so initialise it to a real address or to
NULL. - Forgetting that two names can share one object. Assigning a list or object to a new variable doesn't copy it, so copy it yourself when you need a separate one.
- Returning the address of a local variable from a C function. The local is gone when the function returns, and the caller gets a dangling pointer.
- Freeing the same memory twice, or freeing it while other code still uses it.
- Skipping the null check on a value that can be missing, such as the result of a lookup that finds nothing.
Key takeaways
- A pointer holds the address of a value, not the value itself.
- Following a pointer (dereferencing) reaches the original value, so changes through it are seen by everyone who points there.
- Pointers make it cheap to share large data and let functions change their caller's data without copying.
- A null pointer points nowhere, and a dangling pointer points at memory that has been freed: following either is a bug.
- Higher-level languages use references, the same idea with the address arithmetic hidden and memory freed for you.