-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Object-Oriented C++
-
Section 4: Memory Management
-
Section 5: Templates and Generic Programming
-
Section 6: The Standard Template Library
-
Section 7: Modern C++ Features
-
Section 8: Error Handling
-
Section 9: Multithreading
-
Section 10: Operator Overloading and Type Conversion
-
Section 11: Advanced Topics
-
Section 12: Tooling and Build Systems
-
Section 13: Design Patterns in C++
-
Section 14: Interfacing with C and Systems Programming
-
Section 15: Networking and IPC Basics
-
Section 16: Graphics and Game Programming Basics
-
Section 17: The Boost Libraries
-
Section 18: Data Structures and Algorithms in C++
-
Section 19: Practical Projects
-
Section 20: More Concurrency Patterns
-
Section 21: More OOP and Design Practice
-
Section 22: File I/O and Streams
-
Section 23: More Standard Library
-
Section 24: Practice Exercises
-
Section 25: Interview and Algorithm Practice
-
Section 26: Compiler and Language Internals
-
Section 27: GUI and Application Frameworks Overview
-
Section 28: Testing and Quality Practices
-
Section 29: Numerics Library
-
Section 30: Concepts Library (C++20)
-
Section 31: Ranges Library (C++20) In Depth
-
Section 32: More Utility Library
217: Whiteboard Practice: Detecting a Cycle in a Linked List
When I'm interviewing candidates for C++ roles, the "Linked List Cycle" problem is a classic for a reason. It doesn't just test if you know how pointers work; it tests if you can visualize the movement of data in memory and—more importantly—if you can handle edge cases without crashing the program.
I often see people jump straight into the logic and write something like this. It looks correct at a glance, but it's a landmine:
bool hasCycle(Node* head) {
Node *slow = head;
Node *fast = head;
while (fast != nullptr) {
slow = slow->next;
fast = fast->next->next; // Potential crash here!
if (slow == fast) {
return true;
}
}
return false;
}
The Null Pointer Dereference
If you run this code against a list with an even number of nodes that doesn't have a cycle, it will eventually segfault. Why? Look closely at the while condition and the fast pointer movement.
The loop checks if fast is nullptr. But inside the loop, we call fast->next->next. If fast is the very last node in the list, fast->next is nullptr. Trying to access ->next on a nullptr is an immediate crash. I've seen seasoned devs miss this in the heat of a whiteboard session because they're focusing on the algorithm rather than the memory safety.
Securing the Fast Pointer
To fix this, we need to ensure that both the current node and the next node are valid before we try to leapfrog two spaces ahead. Here is how we handle it properly:
bool hasCycle(Node* head) {
if (!head) return false;
Node *slow = head;
Node *fast = head;
// We must check both fast AND fast->next
while (fast != nullptr && fast->next != nullptr) {
slow = slow->next;
fast = fast->next->next;
if (slow == fast) {
return true;
}
}
return false;
}
Now, the logic is sound. By checking fast->next != nullptr, we guarantee that the call to fast->next->next is safe. If we hit the end of the list, the loop terminates naturally, and we return false because a list with an end, by definition, cannot have a cycle.
Why the Two-Pointer Approach Actually Works
You might be wondering why we use a "slow" and "fast" pointer (often called Floyd's Cycle-Finding Algorithm) instead of just storing every node we've visited in a std::unordered_set. While a set works, it costs you $O(n)$ extra space. In a production environment or a tight memory constraint, that's a waste.
Think of it like two runners on a track. If the track is a straight line, the fast runner just finishes and leaves. But if the track is a loop, the fast runner will eventually lap the slow runner. In our code, the "lap" happens the moment slow == fast. Because the fast pointer moves exactly one node faster than the slow pointer per iteration, the distance between them shrinks by one node each step until they inevitably collide.
The time complexity remains $O(n)$, but the space complexity drops to $O(1)$. That's the kind of optimization that turns a "pass" into a "strong hire" in a technical interview.
📋 Practical Task
Implement a Cycle-Detection Utility for a Custom Linked List
Write a C++ class LinkedListManager that contains a method bool detectCycle(). You will be provided with a Node structure. Your task is to implement the detection logic using the two-pointer approach discussed in the lesson.
To make it challenging:
- Your implementation must handle empty lists (
head == nullptr). - Your implementation must handle lists with only one node (where that node might or might not point back to itself).
- Ensure there are no memory leaks if you create your own test cases to verify the logic.
struct Node {
int data;
Node* next;
Node(int val) : data(val), next(nullptr) {}
};
class LinkedListManager {
public:
Node* head;
LinkedListManager(Node* h) : head(h) {}
bool detectCycle() {
// Your implementation here
}
};There are no comments for now.