What Is a Pointer?
A pointer is a variable that stores the memory address of another variable. Every variable you declare occupies some location in RAM; a pointer lets you work with that location directly. This gives C++ its power and its reputation for both performance and danger.
Understanding pointers is non-negotiable for system programming, competitive programming, and cracking FAANG interviews. Questions on pointers, linked lists, and dynamic memory appear in virtually every C++ technical round at companies like Google India, Amazon, Microsoft, and Adobe.
The Address Operator & and Dereference Operator *
#include <iostream>
using namespace std;
int main() {
int salary = 1500000; // a normal int variable
int* ptr = &salary; // ptr holds the ADDRESS of salary
cout << "Value of salary: " << salary << "\n"; // 1500000
cout << "Address of salary: " << &salary << "\n"; // e.g. 0x7ffee4b3c
cout << "ptr holds: " << ptr << "\n"; // same address
cout << "Value via ptr: " << *ptr << "\n"; // 1500000 (dereference)
*ptr = 1800000; // modify salary through the pointer
cout << "Updated salary: " << salary << "\n"; // 1800000
return 0;
}
&variable— address-of operator: gives the memory address ofvariable.*ptr— dereference operator: gives the value stored at the address inptr.
Declaring Pointers
int* iPtr; // pointer to int
double* dPtr; // pointer to double
char* cPtr; // pointer to char
The * is part of the variable declaration, not the type. It is good practice to initialise pointers immediately.
The Null Pointer
A null pointer points to nothing. Dereferencing it is undefined behaviour and will crash your program on most systems. Always check before dereferencing a pointer that could be null.
int* ptr = nullptr; // C++11 nullptr (preferred)
// int* ptr = NULL; // older C-style, still valid but avoid
if (ptr != nullptr) {
cout << *ptr; // safe
}
Use nullptr (not 0 or NULL) in modern C++.
Pointer Arithmetic
Because array elements are contiguous in memory, you can move through them by adding integers to a pointer. Adding 1 to a pointer advances it by sizeof(element type) bytes — not by 1 byte.
#include <iostream>
using namespace std;
int main() {
int marks[4] = {55, 70, 85, 90};
int* ptr = marks; // points to marks[0]
cout << *ptr << "\n"; // 55
cout << *(ptr + 1) << "\n"; // 70
cout << *(ptr + 2) << "\n"; // 85
ptr++; // advance to marks[1]
cout << *ptr << "\n"; // 70
return 0;
}
The pointer difference (ptr2 - ptr1) gives the number of elements between them, not bytes.
Pointers to Arrays
When an array name is used in an expression, it decays to a pointer to its first element. Pointer arithmetic and array indexing are therefore interchangeable:
int arr[3] = {10, 20, 30};
int* p = arr;
// These are all equivalent:
cout << arr[1] << "\n"; // 20
cout << *(arr+1) << "\n"; // 20
cout << p[1] << "\n"; // 20
cout << *(p+1) << "\n"; // 20
Passing Arrays via Pointers
#include <iostream>
using namespace std;
void doubleAll(int* arr, int size) {
for (int i = 0; i < size; i++) {
arr[i] *= 2;
}
}
int main() {
int scores[5] = {10, 20, 30, 40, 50};
doubleAll(scores, 5);
for (int i = 0; i < 5; i++) {
cout << scores[i] << " "; // 20 40 60 80 100
}
return 0;
}
Pointers to Functions
A function pointer stores the address of a function and lets you call it indirectly — the foundation of callbacks and strategy patterns.
#include <iostream>
using namespace std;
int add(int a, int b) { return a + b; }
int mul(int a, int b) { return a * b; }
int main() {
// Declare a pointer to a function taking two ints, returning int
int (*op)(int, int);
op = add;
cout << op(3, 4) << "\n"; // 7
op = mul;
cout << op(3, 4) << "\n"; // 12
return 0;
}
Function pointers look intimidating at first — reading the declaration inside-out helps: int (*op)(int, int) means "op is a pointer to a function that takes two ints and returns an int."
const with Pointers
The placement of const matters — it controls whether the pointer itself or the pointed-to value can change.
int val = 42;
int other = 99;
const int* ptr1 = &val; // pointer to CONST int
// *ptr1 = 10; // ERROR — cannot change the value
ptr1 = &other; // OK — can change where it points
int* const ptr2 = &val; // CONST pointer to int
*ptr2 = 10; // OK — can change the value
// ptr2 = &other; // ERROR — cannot change where it points
const int* const ptr3 = &val; // const pointer to const int
// Neither *ptr3 nor ptr3 can change
A helpful mnemonic: read the declaration right-to-left. "pointer to const int" vs "const pointer to int".
References
A reference is an alias — another name for an existing variable. Unlike a pointer, a reference cannot be null, cannot be reseated (made to refer to a different variable), and does not need explicit dereferencing.
#include <iostream>
using namespace std;
int main() {
int salary = 1200000;
int& ref = salary; // ref is another name for salary
ref = 1500000; // modifies salary directly
cout << salary << "\n"; // 1500000
return 0;
}
References are most commonly used as function parameters to avoid copying large objects and to allow functions to modify their arguments.
void applyBonus(int& salary, int bonus) {
salary += bonus;
}
int main() {
int ctc = 1000000;
applyBonus(ctc, 200000);
cout << ctc << "\n"; // 1200000
return 0;
}
const References
Passing by const reference is the idiomatic way to accept large objects cheaply without allowing modification:
void printName(const string& name) {
cout << name << "\n";
// name = "other"; // ERROR — const reference
}
Pointers vs References
| Feature | Pointer | Reference |
|---|---|---|
| Syntax | int* p = &x; | int& r = x; |
| Can be null | Yes (nullptr) | No (must bind to a valid object) |
| Can be reseated | Yes (p = &y;) | No (always refers to initial object) |
| Needs dereferencing | Yes (*p) | No (use directly) |
| Can point/refer to arrays | Yes | No (refers to a single object) |
| Arithmetic | Yes (p++) | No |
| Use in dynamic memory | Yes (new/delete) | No |
| Readability | Lower | Higher |
| Preferred for optional values | Yes | No |
| Preferred for function params | Sometimes | Usually |
Worked Example: Swapping Two Numbers
Swapping is a classic demonstration of why pass-by-reference matters.
Approach 1 — Using Pointers
#include <iostream>
using namespace std;
void swapPointers(int* a, int* b) {
int temp = *a;
*a = *b;
*b = temp;
}
int main() {
int x = 500000, y = 750000;
cout << "Before: x = " << x << ", y = " << y << "\n";
swapPointers(&x, &y);
cout << "After: x = " << x << ", y = " << y << "\n";
return 0;
}
Output:
Before: x = 500000, y = 750000
After: x = 750000, y = 500000
Approach 2 — Using References
#include <iostream>
using namespace std;
void swapReferences(int& a, int& b) {
int temp = a;
a = b;
b = temp;
}
int main() {
int x = 500000, y = 750000;
cout << "Before: x = " << x << ", y = " << y << "\n";
swapReferences(x, y); // no & at the call site — cleaner!
cout << "After: x = " << x << ", y = " << y << "\n";
return 0;
}
The reference version is cleaner at the call site. In practice, use std::swap from <algorithm> which is already optimised — but knowing how to implement it yourself is a standard interview expectation.
Common Pitfalls
- Dereferencing a null or uninitialised pointer is undefined behaviour. Always initialise pointers to
nullptrif they have no target yet. - Dangling pointers: A pointer that outlives the variable it points to becomes a dangling pointer. Accessing it is undefined behaviour.
int* dangling; { int local = 42; dangling = &local; } // local is destroyed here // *dangling is now undefined behaviour - Forgetting
&when calling a pointer-parameter function:swapPointers(x, y)will not compile — you must writeswapPointers(&x, &y). - Confusing
const int*andint* const: The position ofconsthas dramatically different meanings. Write small test snippets until the rule is second nature. - References must be initialised:
int& r;is a compile error. A reference must always be bound at declaration. - Pointer arithmetic on non-array pointers: Moving a pointer that does not point into an array (except to one-past-the-end) is undefined behaviour.
Practice Exercises
- Write a function
findMax(int* arr, int size)that returns the maximum element of an array using pointer arithmetic (no square bracket indexing). - Write a function
reverseArray(int* arr, int size)that reverses the array in place using two pointers. - Declare a
const int*and anint* const. Write a short program showing which operations are allowed on each. - Write a function
countOccurrences(const string& str, char ch)that returns how many timeschappears instr, using a reference parameter. - Implement a function pointer table: given an operator character
'+','-','*','/', look up the corresponding function and execute it on two integers. - Write a program that creates two
intvariables representing two candidates' vote counts, swaps them using a pointer-based function, then swaps them back using a reference-based function, printing results after each swap.
Summary
- A pointer stores the memory address of a variable; use
&to get an address and*to dereference. nullptris the correct way to express a null pointer in modern C++; always check before dereferencing.- Pointer arithmetic moves through contiguous memory — advancing by
1movessizeof(type)bytes. - Arrays decay to pointers to their first element; subscript notation and pointer arithmetic are interchangeable.
- Function pointers store the address of a function, enabling callbacks and runtime dispatch.
const int*— the value cannot change through the pointer;int* const— the pointer itself cannot be reseated.- A reference is an alias for an existing variable: it cannot be null, cannot be reseated, and needs no
*syntax. - Prefer pass-by-reference over pass-by-pointer for function parameters when null is not a valid input.
- The swap example shows how both approaches work, with references producing cleaner call-site syntax.