AlgoMaster Logo

Null Pointers

High Priority40 min readUpdated June 6, 2026
Listen to this chapter
Unlock Audio

A null pointer is a pointer that holds a special value meaning "this pointer points at no object". It's a way to give a pointer a defined, checkable "empty" state instead of leaving it pointing at random memory. This chapter covers what a null pointer is, why it exists, how to check for one, and the common bugs that come from forgetting to check before dereferencing.

What a Null Pointer Represents

Every pointer holds an address. Most of the time, that address belongs to a real object that the program can read or write. But there are situations where a pointer variable exists with no object yet to point at:

  • A search function did not find what it was looking for and has nothing to return.
  • A linked list's last node marks the end with a next pointer that points at no further node.
  • An order has an optional discount rule, and most orders don't set one.
  • A configuration field is filled in later, so it starts out empty.

For all of these, the pointer needs a value that means "I'm not pointing at anything right now". That value is what a null pointer carries. Reading the pointer or comparing it is fine. Dereferencing it is not.

Output (typical):

p is a valid int*. It can be copied, returned from a function, stored in a struct, and compared. The only operation that doesn't work is dereferencing it. Writing *p when p is null is undefined behavior, which on most systems shows up as a segmentation fault. The check p == nullptr is the common way to decide whether dereferencing is safe.

The address printed for a null pointer is implementation-defined. Most compilers print 0, some print 0x0, some print (nil). The actual bit pattern is also not required to be all zeros, though on every mainstream platform it is. What the C++ standard guarantees is that a null pointer of type T* compares equal to another null pointer of the same type, and unequal to any pointer that points at a real object.

Why Null Pointers Exist as a Distinct Value

A pointer has to hold some address. If a program declares an int* and doesn't assign anything, the pointer holds whatever bits were already in that memory location, which is almost certainly not a valid address. Dereferencing it is undefined behavior, and there is no way to tell at runtime whether the address is valid.

A designated "no address" value makes "empty" a state the program can check for. With a null pointer, the program can ask "do you point at anything?" before trying to dereference. Without it, every pointer variable would either need a real address from creation or risk corruption that cannot be detected.

Two pointer states then exist as distinct things:

StateWhat it holdsCan it be detected?
Uninitialized (wild)Whatever bits were already thereNo, looks like any other pointer
NullA defined sentinel valueYes, p == nullptr
Pointing at a real objectThe address of a live objectYes, but only the program knows which case it's in

A wild pointer is the dangerous case. To the compiler, it looks like any other pointer; there is no runtime tag that distinguishes "garbage address" from "real address". A null pointer, by contrast, is a known bad state that the program can detect and respond to. That distinction is why the convention is to initialize every pointer either to a real address or to nullptr.

The first two are safe to work with. The third is a bug waiting to be triggered.

Quick Check: Which of these pointer values can be safely passed to a function that checks if (p != nullptr) { *p = 5; }?

  • A) int* a = &someInt;
  • B) int* b = nullptr;
  • C) int* c; (uninitialized local)

<details> <summary>Answer</summary>

A and B. A is a real address, so the check passes and the dereference is safe. B is null, so the check fails and the dereference is skipped. C is undefined behavior the moment it gets used in any way that depends on its value, because the bits in c could form an address that happens to be non-null, and the check would pass and the dereference would corrupt unrelated memory. The check only protects against null, not against garbage.

</details>

NULL and nullptr: A Short Note

C++ has two ways to spell "the null pointer value": the older NULL macro and the newer nullptr keyword. They mean nearly the same thing in plain assignments and comparisons, but they behave differently in subtle situations involving overloading and templates.

NULL is a macro defined as either 0 or ((void*)0), depending on the compiler. It comes from C and predates C++ function overloading. The result is that NULL is essentially an integer in C++ code, which causes problems when the compiler has to pick between overloads. nullptr, added in C++11, is a distinct type (std::nullptr_t) that only ever matches pointer parameters, never integer ones.

The practical guidance for new code is to use nullptr everywhere. NULL and 0 still appear in older codebases. For the rest of this chapter, "null pointer" means "the null-pointer value, however it was spelled".

Checking for Null Before Dereferencing

The most important habit when working with raw pointers is to check before dereferencing whenever the pointer could be null. The check is a one-line conditional, and skipping it is a common cause of segmentation faults in C++ code.

Output:

The check at the top of printProduct is the contract: "this function accepts a null pointer and handles it explicitly". Without the check, the call printProduct(nothing) would dereference null inside the function and crash.

Three common idioms for the check appear interchangeably:

The implicit-bool form works because pointers convert to bool automatically: a null pointer is false, any non-null pointer is true. All three forms compile to the same thing. The explicit form (p == nullptr) reads most clearly and is the recommended style for new code, especially when the codebase distinguishes between "pointer is null" and "pointer is valid but the pointed-to value is falsy".

For chained pointer access, every step needs a check if any of them could be null. A common scenario: a customer object that has an optional shipping address, and the address has an optional phone number.

Output:

Three levels of pointer, three null checks. Skipping any of them risks a crash. This kind of step-by-step guarding is a sign that the data model could use a redesign (perhaps with std::optional, or with required fields instead of pointers), but in legacy code it is the unavoidable shape.

Quick Check: What does this code print?

<details> <summary>Answer</summary>

null. A null pointer converts to false in a boolean context, so if (p) evaluates to false and the else branch runs. The output is null.

</details>

What Happens When a Null Pointer Is Dereferenced

Dereferencing a null pointer is undefined behavior. The C++ standard does not say what happens; it leaves the outcome entirely up to the compiler, the operating system, and the runtime conditions. On modern platforms, the most common outcome is a crash, but that is a courtesy of the operating system, not a guarantee of the language.

A minimal example. This program will crash on every mainstream platform:

Typical output (Linux):

Typical output (macOS):

Typical output (Windows):

A dialog about the program stopping working, or a stack trace in a debugger.

The reason a crash happens on these platforms: address 0 (and a small range around it) is mapped as inaccessible by the operating system. Any attempt to read or write at that address triggers a CPU fault, which the OS turns into a signal that terminates the process. This is a safety net, not a feature of C++.

Where this gets dangerous is on platforms or in situations where address 0 is readable. Some embedded systems map valid hardware registers at address 0. Some optimizers, having proved that a pointer is null, can delete entire blocks of code that would only execute if the pointer were non-null. Both cases produce behavior that looks nothing like a clean crash.

A subtle case: dereferencing a null pointer to access a member can sometimes appear to "work" if the access is only computing an address, not actually touching the memory.

This is still undefined behavior, even though no memory is read. Some compilers print a small offset like 0x4. Others optimize the expression away entirely. The program might not crash, but the language standard makes no promises about what it produces. Treat any operation that starts with a null pointer dereference as broken.

Common Bugs from Null Pointer Dereference

A handful of patterns produce most null-dereference bugs in C++ code. Each one has a recognizable shape and a known fix.

Bug 1: Forgetting to Check After a Lookup

The function findByName returns nullptr when the search misses. The caller forgets to check. The dereference found->name reads through a null pointer and crashes.

Fix: Check the return value before using it.

Bug 2: Dereferencing Before Assignment

The pointer is uninitialized, not null. The check p == nullptr might pass or fail depending on what garbage happened to be in the variable's bits. The check does not protect against a wild pointer.

Fix: Initialize the pointer at the point of declaration.

Now the check works as intended, and the dereference is correctly skipped.

Bug 3: Forgetting That Allocation Can Return Null

The new operator throws std::bad_alloc on failure by default, so most modern C++ code does not return null. But new (std::nothrow) and C-style malloc both return null on failure.

The allocation request is huge enough to fail. malloc returns nullptr. The next line dereferences null.

Fix: Check the return value of any allocation function that can return null.

For C++, new with the throwing behavior is the default, and it never returns null. The check is only needed when explicitly opting out with std::nothrow or when calling into C APIs.

Bug 4: A Member Function Called on a Null Pointer

Calling a member function through a null pointer is undefined behavior, even if the function does not access any members. The this pointer inside the function is null, and any subsequent member access dereferences it.

A confusing wrinkle: this code might appear to "work" if the function does not actually touch *this. Some compilers will let it run without raising an error. The language standard still calls it undefined behavior, and an optimizer is free to assume that this is never null and remove related checks.

Fix: Check the pointer before calling any member function.

Bug 5: Mixing Up Null Pointers and Empty Containers

A null std::vector<int>* is not the same as an empty std::vector<int>. A null pointer does not point at a vector at all, so calling any method on it is undefined behavior. The check cart->empty() is meant to ask "is the vector empty?", but it first has to access the vector through cart, and there is no vector to access.

Fix: Check the pointer first, then the contents.

Better yet, design APIs so that a missing cart is represented by an empty cart, not by a null pointer. Then the check goes away entirely.

Where Null Pointers Show Up in Real APIs

Several standard-library and OS-level APIs use null pointers as a signal. Recognizing the pattern makes it easier to know when a check is required.

APIReturns null when
std::map::find(...)Not directly; returns iterator. End iterator is the sentinel, not nullptr.
std::string::data()Never (always non-null for non-empty strings; nullptr-equivalent for some old impls of empty strings)
std::fopen(...)The file could not be opened
std::malloc(...)The allocation failed
dlopen(...) (POSIX)The library could not be loaded
getenv("VAR")The environment variable is not set
Tree/graph traversal: node->left, node->nextThe current node has no child/successor

The standard C library leans on null pointers heavily because C does not have references or exceptions. Modern C++ code often uses std::optional, exceptions, or std::expected (C++23) for the same job, but legacy and C-style APIs still return nullptr to signal "not available".

For tree and linked-list traversal, the null pointer at the end of a chain is not a bug; it's the structure's terminator. Code that walks these structures has the null check baked into its loop condition:

The loop continues as long as head is not null. When the last node's next is null, the loop ends. This is the canonical use of null pointers as data, not as an error signal.

Quick Check: A function parseInt(const char* text) returns an int on success. How should it signal "parsing failed"?

  • A) Return 0
  • B) Return nullptr
  • C) Take a bool* out-parameter and set it on success/failure
  • D) Take an int* out-parameter, return bool, and use the return for success/failure

<details> <summary>Answer</summary>

D (or std::optional<int> in modern C++). Option A overloads a legitimate result with a failure code. Option B does not compile, because the return type is int, not a pointer. Option C is awkward. Option D is the C-style answer that disambiguates success from result. std::optional<int> is the modern equivalent that avoids the out-parameter entirely.

</details>

Defensive Habits for Code That Uses Raw Pointers

A few rules cover most of the bug surface.

  • Initialize every pointer at declaration. Either to an address (int* p = &x;) or to nullptr. Never leave a pointer variable unassigned.
  • Check before dereferencing whenever the pointer could be null. This includes return values of search functions, optional fields in structs, and any pointer that comes from an external source.
  • Set pointers to `nullptr` after a delete. If the pointer might be checked later, this prevents a use-after-free from looking like a valid dereference.
  • Prefer references for parameters that cannot be null. A T& parameter cannot carry null. This pushes the "can this be missing?" question to the type system, where the compiler can enforce it.
  • Use `std::optional<T>` or `std::expected<T, E>` when "missing" is a meaningful value-level state. These types make the absent case impossible to ignore at the call site, because the type forces a check before access.
  • In ownership-bearing APIs, use smart pointers. A std::unique_ptr<T> or std::shared_ptr<T> carries ownership and can still be null, but the destructor handles cleanup and reduces the surface for use-after-free.

None of these eliminate null pointers entirely. They reduce the places where a forgotten check can cause a crash.

A Worked Example: Looking Up a Customer with Null Handling

A program that finds a customer in a small list and prints their email. The lookup might fail; the customer might not have an email on file.

Output:

Two pointer chains, two null checks, three outcomes. findCustomer returns null when the id is unknown, so the first check catches a missing customer. contactEmail is a pointer field that is null when no email is on file, so the second check catches a customer who exists but has no contact. Both null states are part of the data model, not bugs. Without them, the data model would need a different shape: an "is set" flag next to every optional field, a default empty email object that callers have to distinguish from a real one, or std::optional in place of the raw pointer.

Interview Questions

Q1: What is the difference between a null pointer and an uninitialized pointer?

A null pointer holds a defined sentinel value that compares equal to nullptr. It can be checked, and dereferencing it produces a defined failure (undefined behavior, which on most platforms is a segmentation fault). An uninitialized pointer holds whatever bits happened to be in its memory location, which may or may not look like a real address. The check p == nullptr cannot tell an uninitialized pointer from a real one, so the only safe approach is to initialize every pointer at the point of declaration.

Q2: Why is dereferencing a null pointer undefined behavior rather than a guaranteed crash?

C++ aims to allow efficient compilation across many platforms, including embedded systems where address 0 may map to real memory (such as a hardware register). The standard cannot promise a crash because the runtime conditions differ. Treating the operation as undefined behavior lets the compiler assume that any dereferenced pointer is non-null, which enables optimization, but it also means the program might crash, print garbage, or appear to work depending on the platform and the optimizer.

Q3: When should a function return a pointer instead of a reference, given the possibility of null?

Return a pointer when "no value" is a meaningful and expected outcome the caller must handle, such as a search function whose target may not be in the data. Return a reference when the function always has a real object to return. The reference enforces the "always valid" contract at the type level. In modern C++, std::optional<T> is often preferred over pointer return for value-type results, because it avoids the null-pointer pitfall while still expressing the "missing" case.

Q4: How does `nullptr` differ from `NULL` in C++?

nullptr (C++11) is a keyword of type std::nullptr_t that only converts to pointer types. NULL is a preprocessor macro defined as either 0 or ((void*)0). In overloaded function lookup, NULL often matches integer parameters instead of pointer ones, producing surprising bugs. nullptr always matches pointer overloads. For everyday code, use nullptr.

Q5: A coworker says "this code is fine because we always check for null before dereferencing". What other failure modes does that check not protect against?

The null check does not protect against:

  • Uninitialized pointers that happen to look non-null.
  • Dangling pointers that hold the address of a destroyed object.
  • Pointers into a container that has since been resized or destroyed.
  • Use-after-free, where the memory has been deallocated but the pointer still holds the address.
  • Race conditions where another thread modifies the pointer between the check and the dereference.

A null check guards only against the specific case of "the pointer is exactly equal to null". Many bugs in raw-pointer code come from one of the other categories above.

Exercises

Exercise 1: Write a program that declares an int*, initializes it to nullptr, and prints either "valid" or "null" depending on whether it points at something.

Expected Output:

<details> <summary>Solution</summary>

</details>

Exercise 2: Predict the output.

Expected Output:

<details> <summary>Solution</summary>

A null pointer converts to false in a boolean context, so the first print is false. After p = &x, the pointer holds a real address and converts to true, so the second print is true.

</details>

Exercise 3: Fix the bug.

Expected Output:

<details> <summary>Solution</summary>

find returns nullptr when the name is not in the catalog. The caller has to check before dereferencing.

</details>

Exercise 4: Write a function safePrint(const std::string* s) that prints the string if the pointer is not null, and prints (empty) if it is. Call it with both a valid pointer and a null one.

Expected Output:

<details> <summary>Solution</summary>

</details>

Exercise 5: A linked-list node looks like this:

Write a function length(const Node* head) that returns the number of nodes in the list, using a null check in the loop condition.

Expected Output:

<details> <summary>Solution</summary>

The loop relies on the last node's next being nullptr to terminate. This is the canonical use of a null pointer as data.

</details>

Exercise 6: What's wrong with this code, and how would you fix it?

<details> <summary>Solution</summary>

p is uninitialized, not null. The check might pass or fail depending on what bits happen to be in p's memory. The fix is to initialize p at declaration:

Now the check correctly detects the null state and the dereference is skipped.

</details>

Exercise 7: A struct represents an order with an optional discount pointer:

Write a function finalTotal(const Order& order) that returns the subtotal minus the discount, or the subtotal unchanged if the discount pointer is null.

Expected Output:

<details> <summary>Solution</summary>

</details>

Exercise 8: Why is this idiom safe even though it dereferences a pointer without a check?

<details> <summary>Solution</summary>

The parameter is a reference (const Product&), not a pointer. References cannot be null in well-formed C++ code. The caller has to provide a real object, and the function body can rely on item being a valid Product. No null check is needed inside the function.

</details>

Exercise 9: Predict the output and explain.

<details> <summary>Solution</summary>

The behavior is undefined. Calling a member function through a null pointer passes nullptr as the this pointer. Inside greet, the access to id dereferences this, which is null, so the program will most likely crash with a segmentation fault on a modern platform. Some optimizers may also delete the call entirely after deducing that this cannot be null in well-formed code. Either way, the output is not predictable, and the code should never be written this way.

</details>

Exercise 10: Rewrite this function so that the missing-value case is encoded in the type system instead of via a null pointer.

<details> <summary>Solution</summary>

std::optional<std::string> is empty when no value is present and holds a string when one is. The caller checks with has_value() or uses value_or(default). The compiler enforces the check before access (through operator* or value()), so forgetting to check is harder than with a raw pointer. The function no longer needs to return a pointer at all.

</details>

Quiz

Null Pointers Quiz

10 quizzes