Skip to main content

LinkedList in Java

LinkedList Deep Dive: Internal Backend Working

The LinkedList is one of the most mechanically fascinating data structures in the Java Collection Framework. While ArrayList is backed by a continuous block of memory (an array), LinkedList scatters its data across RAM and connects it using intelligent pointer references.

Found in the java.util package, its internal hierarchy looks like this:

Iterable ↓ Collection ↓ List & Deque ↓ LinkedList

1. What exactly is a LinkedList?

Internally, a Java LinkedList is implemented as a Doubly Linked List. This means every single element you insert is wrapped inside a standalone object called a Node.

Each Node acts as an isolated island in memory containing three vital pieces of information:

  • Data: The actual value or object you are storing.
  • Next: The memory address pointing to the next node.
  • Prev: The memory address pointing to the previous node.

The Internal Node Structure

If you look inside the JDK source code, the Node class is elegantly simple:

private static class Node<E> { E item; Node<E> next; Node<E> prev; Node(Node<E> prev, E element, Node<E> next) { this.item = element; this.next = next; this.prev = prev; } }

2. Memory Representation & Head/Tail Pointers

Because the nodes are scattered, the LinkedList class must maintain strict pointers to the absolute beginning and end of the chain so it never loses track of the data.

transient int size = 0; transient Node<E> first; // Pointer to Head transient Node<E> last; // Pointer to Tail

If we execute list.add(10); list.add(20); list.add(30);, the backend weaves them together like this:

first
null 10
20
30 null
last

3. Backend Working: addLast() and addFirst()

Appending Data: add(e)

When you call list.add(40);, Java triggers a private method called linkLast(e). Because the list maintains a direct pointer to the last node, no traversal is required.

  • Step 1: Create a new node [40].
  • Step 2: Set the new node's prev to point to the old last node.
  • Step 3: Set the old last node's next to point to the new node.
  • Step 4: Update the main last pointer to the new node.

Time Complexity: $O(1)$.

Prepending Data: addFirst(e)

Similarly, list.addFirst(5); calls linkFirst(e). It simply hooks a new node onto the first pointer. Unlike ArrayList, absolutely no elements are shifted. The memory remains exactly where it is; only the reference arrows change.

Time Complexity: $O(1)$.


4. The get(index) Optimization: Bidirectional Traversal

Fetching an element by index in a LinkedList is its biggest weakness. Because nodes are not stored sequentially in memory, Java cannot perform instant math to find them. It must traverse the links one by one. But Java uses a brilliant optimization inside its internal node(index) method.

Node<E> node(int index) { // If index is in the first half of the list if (index < (size >> 1)) { Node<E> x = first; for (int i = 0; i < index; i++) x = x.next; return x; } else { // If index is in the second half Node<E> x = last; for (int i = size - 1; i > index; i--) x = x.prev; return x; } }

If you have 100 nodes and ask for index 90, Java realizes 90 is past the halfway mark (size >> 1). Instead of starting at the first node and counting up 90 times, it starts at the last node and counts backwards 10 times. However, the time complexity remains $O(n)$.


5. Removing Elements: The unlink() Process

When you call list.remove(1);, Java locates the node (using the traversal logic above) and passes it to unlink(Node<E> x).

To remove a node, Java simply reaches across it. It tells the previous node to point directly to the next node, completely bypassing the target. The target node becomes disconnected, and the Garbage Collector destroys it.

The Myth of O(1) Middle Insertions/Deletions

You will frequently hear that LinkedList insertions and deletions are $O(1)$. This is a half-truth.

The actual physical act of updating the prev and next pointers is instantaneous $O(1)$. However, finding the middle node in the first place requires an $O(n)$ traversal. Therefore, inserting or deleting in the middle of a LinkedList is effectively $O(n)$.


6. Memory Overhead & The CPU Cache Problem

Why is LinkedList rarely used in high-performance modern backends?

  • Memory Heaviness: An ArrayList only stores the references to your data. A LinkedList must store the data plus two entirely separate memory addresses (prev and next) for every single element.
  • Cache Misses: Modern CPUs are optimized to pre-load continuous blocks of memory (like arrays) into their ultra-fast L1/L2 caches. Because LinkedList nodes are scattered randomly across the heap, the CPU constantly suffers "cache misses," forcing it to fetch data from the significantly slower main RAM.

7. Deque, Queue, and Stack Operations

Because adding and removing from the absolute beginning and end of a LinkedList is a guaranteed $O(1)$ operation, it implements the Deque interface, making it perfect for Stacks and Queues.

Behavior Implementation Methods Used
Queue (FIFO) Queue<Integer> q = new LinkedList<>(); offer(), poll(), peek()
Stack (LIFO) Deque<Integer> s = new LinkedList<>(); push(), pop(), peek()

8. ArrayList vs. LinkedList Backend

Feature ArrayList LinkedList
Backend Structure Dynamic Array Doubly Linked List
Memory Allocation Contiguous Scattered Nodes
Memory Overhead Low High (extra pointers)
Cache Friendly? Yes No
get(index) $O(1)$ $O(n)$
addFirst() / removeFirst() $O(n)$ (Shifting required) $O(1)$ (Pointer update)

When to actually use LinkedList?

In modern Java, ArrayList outperforms LinkedList in almost every practical scenario due to CPU caching—even for middle insertions. You should strictly reserve LinkedList for:

  • Building Queues or Deques where you are exclusively adding/removing from the absolute head and tail.
  • Systems like a Browser History (Back/Forward), Music Playlists, or Undo/Redo functionality where bidirectional pointer traversal is inherently required by the business logic.
  • Scenarios where you have zero read/search requirements, but massive amounts of continuous head/tail insertions where the cost of an ArrayList array-resize would cause latency spikes.

Comments

Popular posts from this blog

How I Got Selected in MNC

Virtusa Sometimes success does not come from having the best coding skills or the perfect roadmap. Sometimes it comes from simply refusing to quit. This is the honest story of how I transitioned from a confused, rejected fresher to getting selected as an Associate Engineer at Virtusa. The Beginning: Confused About My Future After completing my graduation, I stared blankly at my career options. Like many freshers, I lacked a clear direction. Should I join a Java course? Should I prepare on my own? Should I just wait for campus placement opportunities? One day, I called my friend Chetan. He suggested I join Naresh i Technologies and start learning Java seriously. Still unsure of my path, I told him I needed time to think about it. A couple of days later, my phone buzzed with a WhatsApp message offering a job opportunity. They asked me to come for the next round of the recruitment process. Excitement completely took over. I packed my bags, traveled to th...

Spring Boot Introduction

Spring Boot Introduction: Architecture, Dependencies, and Embedded Servers Modern enterprise applications demand rapid development, frictionless deployment, and absolute minimal configuration. Before Spring Boot arrived, developers utilizing the Spring Framework wasted immense amounts of time configuring XML files, managing clashing dependencies, setting up clunky application servers, and stitching various Spring modules together manually. To eliminate these bottlenecks, Pivotal introduced Spring Boot . Built entirely on top of the traditional Spring Framework, Spring Boot is an "opinionated" framework. It aggressively simplifies application development by injecting auto-configuration, packaging starter dependencies, and embedding web servers directly into your application. This allows backend developers to focus entirely on building business logic rather than wrestling with infrastructure setup. What is Spring Boot? Spring Boot is a powerful extens...

Strings in C

C Programming: Working with Strings Unlike modern programming languages like Python or Java, C does not possess a dedicated "String" data type. Instead, C treats a string as a simple 1D array of characters. Real-life example: Think of a freight train. The train does not exist as one solid object; it consists of individual boxcars linked together. Similarly, a C string links individual characters side-by-side in memory. To let the computer know the train has ended, C attaches a special "caboose" called the Null Terminator ( \0 ). 1. Essential String Functions Handling strings manually requires complex loops. To save time, C provides a built-in library called <string.h> that contains powerful functions to manipulate text. strlen(): You use this to find the exact length of a string. The compiler counts the characters until it hits the \0 terminator. It does not count the terminator itself. strcpy(): You ...