Pointers are the concept that separates people who understand how computers work from people who use them as black boxes. You do not need to write pointer arithmetic in daily application work — but when a Python variable "unexpectedly" changes, when a JavaScript object mutation surprises you, or when a C++ program segfaults, the answer is almost always a pointer you did not realise you were holding.
What changed in 2026
- Rust is now taught in most university systems courses. Its ownership model makes pointer safety guarantees at compile time, and graduates entering the workforce understand borrow semantics at a deeper level than typical.
- C++ 23
std::mdspan standardised multi-dimensional spans. Raw pointer arithmetic for array views is now largely replaced by spans in modern C++.
- Swift 6 strict concurrency made Sendable enforcement ubiquitous. Shared mutable state accessed across actors is now a compile error, reducing a whole class of data-race pointer bugs.
- WebAssembly linear memory keeps pointer concepts relevant in browser code. WASM modules written in C, C++, or Rust expose pointer-like memory offsets to JavaScript callers.
What is a pointer?
A pointer is a variable whose value is a memory address — specifically, the address of another variable or heap allocation.
int x = 42;
int *p = &x; // p holds the address of x
printf("%d\n", *p); // dereference: read value at address → 42
*p = 100; // write through pointer: x is now 100
printf("%d\n", x); // → 100
&x is the "address-of" operator. *p is the "dereference" operator. These two are inverses.
Stack vs heap memory
| Memory region |
Allocation |
Lifetime |
Who manages it |
| Stack |
Automatic (function call) |
Scope-bound |
Compiler |
| Heap |
Explicit (malloc, new, Box::new) |
Arbitrary |
Developer (or GC/owner) |
Local variables live on the stack and are automatically freed when the function returns. Heap allocations live until explicitly freed — or until a garbage collector or smart pointer frees them.
fn stack_example() {
let x = 42; // stack-allocated; freed when function returns
let y = Box::new(42); // heap-allocated; freed when `y` drops (end of scope)
}
Null pointer dereference
Dereferencing a null/nil pointer crashes the program. It is the most common pointer bug across all languages.
int *p = NULL;
*p = 42; // Segmentation fault — undefined behaviour
Modern languages made null safety a first-class feature:
// Kotlin — nullable type forces null check at compile time
var p: String? = null
p.length // Compile error: unsafe call
p?.length // Safe: returns null if p is null
p!!.length // Runtime crash if null — explicit opt-in
TypeScript's strictNullChecks, Rust's Option<T>, and Swift's Optional all eliminate unintentional null dereferences.
References vs pointers
Most high-level languages expose references — pointers with restrictions that make them safer.
| Feature |
Raw pointer (C) |
Reference (C++) |
Rust borrow |
Java/Python reference |
| Can be null |
Yes |
No (must bind) |
No (Option<&T> for nullable) |
Yes (null / None) |
| Arithmetic |
Yes |
No |
No |
No |
| Multiple writers |
Yes |
Yes |
No (borrow checker) |
Yes (with GC) |
| Freed incorrectly |
Yes (UB) |
Possible |
Compile error |
No (GC handles) |
Smart pointers in C++ and Rust
Manual malloc/free or new/delete leads to memory leaks, use-after-free, and double-free. Smart pointers encode ownership in the type system.
// C++ — unique ownership: freed when unique_ptr goes out of scope
#include <memory>
auto p = std::make_unique<int>(42);
// No delete needed; destructor runs automatically
// Shared ownership: freed when last shared_ptr is destroyed
auto s1 = std::make_shared<int>(42);
auto s2 = s1; // ref count = 2
// freed when both s1 and s2 go out of scope
// Rust — ownership makes this a compile-time guarantee
let b = Box::new(42); // unique_ptr equivalent
// b is freed when it goes out of scope; no manual free
let rc = std::rc::Rc::new(42); // shared_ptr equivalent (single-threaded)
let arc = std::sync::Arc::new(42); // shared_ptr equivalent (thread-safe)
How objects pass in high-level languages
Python and Java pass object references by value — the reference (pointer) is copied, not the object.
def modify(lst):
lst.append(4) # modifies the original object — same reference
def replace(lst):
lst = [1, 2, 3] # rebinds local variable — original unchanged
data = [1, 2, 3]
modify(data) # data is now [1, 2, 3, 4]
replace(data) # data is still [1, 2, 3, 4]
"Pass by value" vs "pass by reference" debates in Python are really about this: object references are passed by value. You can mutate through the reference but cannot rebind the caller's variable.
How to pick the right memory model
- High-level language (Python, JS, Go, Java)? References are managed; focus on mutation semantics and null safety.
- C++ new code? Use
unique_ptr / shared_ptr; avoid raw new/delete.
- Rust? The borrow checker enforces correct ownership; lean into it instead of fighting it.
- C or embedded? Raw pointers are unavoidable; use AddressSanitizer and Valgrind to catch bugs.
- Need to share across threads? Use
Arc (Rust), shared_ptr + mutex (C++), or channel-based message passing.
Common mistakes
Dangling pointer. A pointer to a stack variable that has been freed.
int *dangling() {
int x = 42;
return &x; // x freed when function returns; pointer is now dangling
}
Memory leak. Allocating heap memory and losing all references without freeing.
Use-after-free. Accessing memory after freeing it — undefined behaviour that can be exploited for security vulnerabilities.
Aliasing through raw pointers. Two pointers to the same memory, one mutable — violates Rust's borrow rules and causes data races in C/C++.
What to skip
- Raw pointer arithmetic in C++ for array access — use
std::span (C++20) or std::vector iterators.
unsafe Rust blocks for routine code — the safe subset covers 99 % of practical needs. See How to handle errors gracefully in 2026 for Rust-safe error patterns.
- Shared mutable state across threads without synchronisation — always protect with a mutex, atomic, or channel regardless of language.
FAQ
Why do C pointers cause so many security vulnerabilities?
Pointer arithmetic without bounds checks allows buffer overflows. An attacker controls the overflow data, overwriting return addresses or function pointers. This is the basis of most memory-corruption exploits. Languages with bounds-checked arrays eliminate this class.
What is a fat pointer?
A pointer paired with metadata — typically a length. Rust slices (&[T]) are fat pointers: 8 bytes for the address + 8 bytes for the length. They enable bounds-checked access without a separate length variable.
Is garbage collection just automatic pointer management?
Essentially yes. The GC tracks which heap objects are still reachable (via reference chains from roots) and frees unreachable ones. Reference counting (Python, Swift, Rust Rc) is a simpler form of GC.
What is a void pointer?
In C/C++, void * is a pointer to an untyped memory location. You can cast it to any other pointer type. It is the mechanism behind malloc returning void * — the caller casts to the appropriate type.
Where to go next