
Java LinkedList Tutorial: How to Use LinkedList in Java
Java’s LinkedList is a doubly-linked list implementation that serves as a fundamental data structure when you need frequent insertions and deletions at both ends of a collection. Unlike ArrayList which uses a dynamic array underneath, LinkedList maintains references between nodes, making it particularly useful for scenarios where you’re building queues, stacks, or need constant-time insertion/deletion operations. This tutorial will walk you through everything you need to know about implementing and optimizing LinkedList operations, from basic usage to performance considerations and real-world applications that you’ll actually encounter in production environments.
How LinkedList Works Under the Hood
LinkedList in Java implements both List and Deque interfaces, giving you flexibility in how you interact with the data structure. Each element (node) contains three parts: the data, a reference to the previous node, and a reference to the next node. This doubly-linked structure allows bidirectional traversal but comes with memory overhead compared to arrays.
The internal structure looks like this:
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;
}
}
The LinkedList class maintains references to both the first and last nodes, enabling O(1) insertions and deletions at both ends. However, accessing elements by index requires traversal from either end, resulting in O(n) time complexity for random access operations.
Basic LinkedList Implementation and Setup
Getting started with LinkedList is straightforward. Here’s how to create and manipulate a basic LinkedList:
import java.util.LinkedList;
import java.util.ListIterator;
public class LinkedListExample {
public static void main(String[] args) {
// Create a new LinkedList
LinkedList<String> servers = new LinkedList<>();
// Add elements at the end
servers.add("web-server-01");
servers.add("db-server-01");
servers.add("cache-server-01");
// Add elements at specific positions
servers.addFirst("load-balancer");
servers.addLast("backup-server");
servers.add(2, "api-server-01");
// Display the list
System.out.println("Server list: " + servers);
// Access elements
String firstServer = servers.getFirst();
String lastServer = servers.getLast();
String secondServer = servers.get(1);
System.out.println("First: " + firstServer);
System.out.println("Last: " + lastServer);
System.out.println("Second: " + secondServer);
}
}
For more advanced operations, you can use LinkedList as a queue or stack:
// Using LinkedList as a Queue (FIFO)
LinkedList<String> taskQueue = new LinkedList<>();
taskQueue.offer("backup-database");
taskQueue.offer("update-certificates");
taskQueue.offer("restart-services");
String nextTask = taskQueue.poll(); // Returns and removes "backup-database"
// Using LinkedList as a Stack (LIFO)
LinkedList<String> operationStack = new LinkedList<>();
operationStack.push("connect-to-server");
operationStack.push("authenticate-user");
operationStack.push("execute-command");
String lastOperation = operationStack.pop(); // Returns and removes "execute-command"
Performance Analysis and Comparisons
Understanding when to use LinkedList versus other collections is crucial for application performance. Here’s a comprehensive comparison:
Operation | LinkedList | ArrayList | ArrayDeque |
---|---|---|---|
Add at beginning | O(1) | O(n) | O(1) |
Add at end | O(1) | O(1) amortized | O(1) |
Random access | O(n) | O(1) | O(1) |
Remove by index | O(n) | O(n) | O(n) |
Memory overhead | High (3 references per node) | Low | Low |
Iterator performance | Good | Excellent | Excellent |
Here’s a practical performance test you can run to see the differences:
import java.util.*;
public class PerformanceTest {
private static final int ITERATIONS = 100000;
public static void main(String[] args) {
// Test insertion at beginning
testInsertionAtBeginning();
// Test random access
testRandomAccess();
}
private static void testInsertionAtBeginning() {
// LinkedList test
long startTime = System.nanoTime();
LinkedList<Integer> linkedList = new LinkedList<>();
for (int i = 0; i < ITERATIONS; i++) {
linkedList.addFirst(i);
}
long linkedListTime = System.nanoTime() - startTime;
// ArrayList test
startTime = System.nanoTime();
ArrayList<Integer> arrayList = new ArrayList<>();
for (int i = 0; i < ITERATIONS; i++) {
arrayList.add(0, i);
}
long arrayListTime = System.nanoTime() - startTime;
System.out.println("Insertion at beginning:");
System.out.println("LinkedList: " + linkedListTime / 1_000_000 + " ms");
System.out.println("ArrayList: " + arrayListTime / 1_000_000 + " ms");
}
private static void testRandomAccess() {
LinkedList<Integer> linkedList = new LinkedList<>();
ArrayList<Integer> arrayList = new ArrayList<>();
// Populate lists
for (int i = 0; i < ITERATIONS; i++) {
linkedList.add(i);
arrayList.add(i);
}
Random random = new Random();
// Test LinkedList random access
long startTime = System.nanoTime();
for (int i = 0; i < 10000; i++) {
int index = random.nextInt(ITERATIONS);
linkedList.get(index);
}
long linkedListTime = System.nanoTime() - startTime;
// Test ArrayList random access
startTime = System.nanoTime();
for (int i = 0; i < 10000; i++) {
int index = random.nextInt(ITERATIONS);
arrayList.get(index);
}
long arrayListTime = System.nanoTime() - startTime;
System.out.println("Random access:");
System.out.println("LinkedList: " + linkedListTime / 1_000_000 + " ms");
System.out.println("ArrayList: " + arrayListTime / 1_000_000 + " ms");
}
}
Real-World Use Cases and Examples
LinkedList shines in specific scenarios. Here are some practical applications you might encounter:
Building a Request Processing Queue
When managing server requests that need to be processed in order, LinkedList provides an efficient queue implementation:
import java.util.LinkedList;
import java.util.concurrent.locks.ReentrantLock;
public class RequestProcessor {
private final LinkedList<ServerRequest> requestQueue;
private final ReentrantLock lock;
private volatile boolean isProcessing;
public RequestProcessor() {
this.requestQueue = new LinkedList<>();
this.lock = new ReentrantLock();
this.isProcessing = false;
}
public void addRequest(ServerRequest request) {
lock.lock();
try {
requestQueue.offer(request);
System.out.println("Added request: " + request.getId());
if (!isProcessing) {
processRequests();
}
} finally {
lock.unlock();
}
}
private void processRequests() {
isProcessing = true;
new Thread(() -> {
while (true) {
ServerRequest request = null;
lock.lock();
try {
request = requestQueue.poll();
if (request == null) {
isProcessing = false;
break;
}
} finally {
lock.unlock();
}
// Process the request
handleRequest(request);
}
}).start();
}
private void handleRequest(ServerRequest request) {
try {
// Simulate request processing
Thread.sleep(100);
System.out.println("Processed request: " + request.getId());
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
public static class ServerRequest {
private final String id;
private final String data;
public ServerRequest(String id, String data) {
this.id = id;
this.data = data;
}
public String getId() { return id; }
public String getData() { return data; }
}
}
Implementing an LRU Cache
LinkedList can be part of a Least Recently Used cache implementation, though you’d typically combine it with a HashMap for O(1) access:
import java.util.HashMap;
import java.util.LinkedList;
public class LRUCache<K, V> {
private final int capacity;
private final LinkedList<CacheNode<K, V>> list;
private final HashMap<K, CacheNode<K, V>> map;
public LRUCache(int capacity) {
this.capacity = capacity;
this.list = new LinkedList<>();
this.map = new HashMap<>();
}
public V get(K key) {
CacheNode<K, V> node = map.get(key);
if (node == null) {
return null;
}
// Move to front (most recently used)
list.remove(node);
list.addFirst(node);
return node.value;
}
public void put(K key, V value) {
CacheNode<K, V> existingNode = map.get(key);
if (existingNode != null) {
// Update existing node
existingNode.value = value;
list.remove(existingNode);
list.addFirst(existingNode);
} else {
// Add new node
if (list.size() >= capacity) {
// Remove least recently used
CacheNode<K, V> lru = list.removeLast();
map.remove(lru.key);
}
CacheNode<K, V> newNode = new CacheNode<>(key, value);
list.addFirst(newNode);
map.put(key, newNode);
}
}
private static class CacheNode<K, V> {
K key;
V value;
CacheNode(K key, V value) {
this.key = key;
this.value = value;
}
}
}
Best Practices and Common Pitfalls
Here are the key considerations when working with LinkedList in production environments:
- Avoid random access: Never use get(index) in loops. Use iterators instead for traversal.
- Memory considerations: LinkedList uses more memory per element due to node overhead. For VPS environments with limited memory, consider alternatives.
- Thread safety: LinkedList is not thread-safe. Use Collections.synchronizedList() or concurrent alternatives.
- Iterator behavior: Use ListIterator for bidirectional traversal and safe modification during iteration.
Here’s an example of proper iteration techniques:
LinkedList<String> servers = new LinkedList<>();
servers.add("server1");
servers.add("server2");
servers.add("server3");
// Bad: Using index-based access
for (int i = 0; i < servers.size(); i++) {
System.out.println(servers.get(i)); // O(n) for each access!
}
// Good: Using enhanced for loop
for (String server : servers) {
System.out.println(server);
}
// Good: Using iterator for modifications
Iterator<String> iterator = servers.iterator();
while (iterator.hasNext()) {
String server = iterator.next();
if (server.startsWith("old")) {
iterator.remove(); // Safe removal during iteration
}
}
// Excellent: Using ListIterator for bidirectional access
ListIterator<String> listIterator = servers.listIterator();
while (listIterator.hasNext()) {
String server = listIterator.next();
if (server.equals("target")) {
listIterator.add("new-server"); // Insert after current element
}
}
Integration with Server Applications
When deploying applications that use LinkedList on dedicated servers, consider these optimization strategies:
import java.util.LinkedList;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
public class ServerOptimizedQueue {
// For single-threaded scenarios
private final LinkedList<Task> singleThreadQueue = new LinkedList<>();
// For multi-threaded scenarios
private final BlockingQueue<Task> multiThreadQueue = new LinkedBlockingQueue<>();
public void configureFerHighThroughput() {
// Pre-size collections when possible
LinkedList<String> buffer = new LinkedList<>();
// Batch operations for better performance
batchProcess(buffer);
}
private void batchProcess(LinkedList<String> items) {
final int BATCH_SIZE = 1000;
while (!items.isEmpty()) {
LinkedList<String> batch = new LinkedList<>();
for (int i = 0; i < BATCH_SIZE && !items.isEmpty(); i++) {
batch.add(items.removeFirst());
}
processBatch(batch);
}
}
private void processBatch(LinkedList<String> batch) {
// Process batch efficiently
batch.forEach(this::processItem);
}
private void processItem(String item) {
// Individual item processing logic
}
public static class Task {
private final String id;
private final Runnable action;
public Task(String id, Runnable action) {
this.id = id;
this.action = action;
}
public void execute() {
action.run();
}
}
}
Common troubleshooting scenarios include memory leaks from retaining references and performance degradation from improper usage patterns. Monitor your application’s memory usage and profile LinkedList operations in production to identify bottlenecks.
For additional reference, consult the official Java LinkedList documentation for complete method specifications and implementation details.

This article incorporates information and material from various online sources. We acknowledge and appreciate the work of all original authors, publishers, and websites. While every effort has been made to appropriately credit the source material, any unintentional oversight or omission does not constitute a copyright infringement. All trademarks, logos, and images mentioned are the property of their respective owners. If you believe that any content used in this article infringes upon your copyright, please contact us immediately for review and prompt action.
This article is intended for informational and educational purposes only and does not infringe on the rights of the copyright owners. If any copyrighted material has been used without proper credit or in violation of copyright laws, it is unintentional and we will rectify it promptly upon notification. Please note that the republishing, redistribution, or reproduction of part or all of the contents in any form is prohibited without express written permission from the author and website owner. For permissions or further inquiries, please contact us.