Java
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Object-Oriented Java
-
Section 4: Collections Framework
-
Section 5: Exception Handling
-
Section 6: Generics
-
Section 7: Functional Java
-
Section 8: Concurrency
-
Section 9: I/O and NIO
-
Section 10: JVM Internals
-
Section 11: Modern Java Features
-
Section 12: Build Tools and Project Structure
-
Section 13: Testing
-
Section 14: Databases and Persistence
-
Section 15: Networking
-
Section 16: Design and Best Practices
-
Section 17: Reflection and Annotations
-
Section 18: Logging and Diagnostics
-
Section 19: Date, Time, and Internationalization
-
Section 20: Java Platform Module System
-
Section 21: Security in Java
-
Section 22: Advanced Collections and Data Structures
-
Section 23: More Concurrency Patterns
-
Section 24: Compression, Files, and System Integration
-
Section 25: GUI Programming
-
Section 26: Practical Projects
-
Section 27: Data Structures and Algorithms
-
Section 28: Interview and Algorithm Practice
-
Section 29: JSON and Data Interchange
-
Section 30: More Concurrency Utilities
-
Section 31: More Collections and Streams Practice
-
Section 32: More File and System Programming
-
Section 33: Standard Library Deep Dive
-
Section 34: More Practice and Drills
-
Section 35: More Testing and Quality
-
Section 36: More Design Patterns and Architecture
-
Section 37: Career and Ecosystem
-
Section 38: More OOP and Architecture Practice
-
Section 39: More Enterprise Concepts
-
Section 40: Advanced JavaFX
-
Section 41: More Interview Practice
190: Implementing a Custom Linked List
When you first start building your own data structures, it's easy to get tripped up by how Java handles references. I remember early in my career thinking that if I just "moved" a pointer past a node, that node was gone. It's a common misconception that leads to a very specific, frustrating bug.
Take a look at this implementation of a remove method for a custom singly linked list. On the surface, it looks logical: find the node with the target value, and then move on.
public void remove(int value) {
Node current = head;
while (current != null) {
if (current.data == value) {
// I found it! Now just skip it.
current = current.next;
return;
}
current = current.next;
}
}
The "Vanishing" Node That Doesn't Actually Vanish
If you run this code and then print your list, you'll notice something annoying: the node you tried to remove is still there. You didn't get a NullPointerException, and the code didn't crash; it just... didn't work.
Here is why: In Java, current is just a local reference variable. When you do current = current.next, you aren't changing the structure of the linked list; you're just changing which node the local variable current happens to be pointing to at that exact microsecond. The previous node in the list is still holding a reference to the node you want to delete. Since the chain isn't broken, the Garbage Collector won't touch that node, and it stays right where it was.
Tracking the Previous Node to Patch the Gap
To actually remove a node from a singly linked list, you have to change the next reference of the node before the one you're deleting. You need to "stitch" the previous node directly to the following node, effectively jumping over the victim.
The cleanest way to do this is to keep track of a previous pointer as you traverse. Here is how I would rewrite that logic:
public void remove(int value) {
if (head == null) return;
// Special case: removing the head
if (head.data == value) {
head = head.next;
return;
}
Node current = head;
Node previous = null;
while (current != null) {
if (current.data == value) {
// The magic happens here:
// We tell the previous node to point to the one AFTER current.
previous.next = current.next;
return;
}
previous = current; // Keep track of where we were
current = current.next;
}
}
By updating previous.next, you've officially removed the node from the chain. The node that was once current is now unreachable from the head of the list, meaning Java's garbage collector will reclaim that memory.
Managing the Head and Tail Edge Cases
Whenever you implement a custom list, you have to obsess over the edges. I've seen countless production bugs caused by developers forgetting that the first and last elements behave differently.
- The Head: If you remove the first element, there is no
previousnode. You must update theheadreference itself, otherwise your list still starts at the node you intended to delete. - The Tail: If you remove the last element,
current.nextisnull. My fix above handles this naturally becauseprevious.nextsimply becomesnull, correctly marking the new end of the list. - Empty Lists: Always check if
head == nullimmediately. Trying to accesshead.dataon an empty list is the fastest way to trigger aNullPointerException.
📋 Practical Task
Implement a "Remove Last" Method for a Custom Singly Linked List
You have been provided with a basic CustomLinkedList class that includes a Node inner class and an add(int value) method. Your task is to implement the removeLast() method.
Requirements:
- The method should remove the very last node in the list.
- If the list is empty, the method should do nothing.
- If the list has only one element, the
headshould becomenull. - If the list has multiple elements, the second-to-last node's
nextreference must be set tonull.
public class CustomLinkedList {
private Node head;
private static class Node {
int data;
Node next;
Node(int data) { this.data = data; }
}
public void add(int value) {
if (head == null) {
head = new Node(value);
return;
}
Node current = head;
while (current.next != null) {
current = current.next;
}
current.next = new Node(value);
}
public void removeLast() {
// TODO: Implement this logic
}
public void printList() {
Node current = head;
while (current != null) {
System.out.print(current.data + " -> ");
current = current.next;
}
System.out.println("null");
}
}There are no comments for now.