DSA Tutorial
🔍

Linked List Collection Frameworks

Why Use Built-in Implementations?

Every major language ships a production-quality linked list implementation in its standard library. These implementations are:

  • Tested — battle-hardened against edge cases you might miss
  • Optimized — memory-efficient internal representations
  • Feature-rich — dozens of utility methods built in
  • Thread-safe variants — available where concurrency matters

In interviews and production code, you almost never implement a linked list from scratch. You use the built-in and focus on the algorithm. Understanding what the built-in provides — and its complexity for each operation — is what separates strong engineers from beginners.

Language Overview

Language    Primary Class         Secondary / Alternatives
────────────────────────────────────────────────────────────────
Java        LinkedList<E>         ArrayDeque<E>, Deque<E>
Python      collections.deque     list (array-backed, not linked)
C++         std::list<T>          std::forward_list<T>
JavaScript  No built-in LL        Array (array-backed), custom class

JavaScript has no built-in linked list — the native Array is array-backed. For interview problems in JavaScript, implement a simple Node class or use the Array with awareness of its O(n) shift costs.

Java: LinkedList<E>

Java's LinkedList is a doubly linked list that implements both the List and Deque interfaces. It gives you list operations (index access, insert at position) and deque operations (O(1) add/remove at both ends) in one class.

Declaration and Initialization

Java
1import java.util.LinkedList; 2import java.util.List; 3import java.util.Deque; 4 5public class JavaLinkedList { 6 7 public static void main(String[] args) { 8 // As a general linked list 9 LinkedList<Integer> list = new LinkedList<>(); 10 11 // As a List interface (read-only reference — limits to List methods) 12 List<Integer> asList = new LinkedList<>(); 13 14 // As a Deque interface (recommended for queue/stack use) 15 Deque<Integer> deque = new LinkedList<>(); 16 17 // Initialize from a collection 18 LinkedList<String> names = new LinkedList<>( 19 java.util.Arrays.asList("Alice", "Bob", "Charlie") 20 ); 21 22 System.out.println("Names: " + names); // [Alice, Bob, Charlie] 23 System.out.println("Size: " + names.size()); // 3 24 } 25}
Output (Java):
Names: [Alice, Bob, Charlie]
Size:  3

Java LinkedList — Core Operations

Java
1import java.util.LinkedList; 2 3public class LinkedListOps { 4 5 public static void main(String[] args) { 6 LinkedList<Integer> list = new LinkedList<>(); 7 8 // ── ADD ────────────────────────────────────────── 9 list.add(20); // Add to tail — O(1) 10 list.add(30); 11 list.addFirst(10); // Add to head — O(1) 12 list.addLast(40); // Add to tail — O(1) 13 list.add(2, 25); // Add at index — O(n) 14 System.out.println("After adds: " + list); // [10, 20, 25, 30, 40] 15 16 // ── ACCESS ─────────────────────────────────────── 17 System.out.println("First: " + list.getFirst()); // 10 — O(1) 18 System.out.println("Last: " + list.getLast()); // 40 — O(1) 19 System.out.println("Index 2: " + list.get(2)); // 25 — O(n) 20 System.out.println("Peek head: " + list.peek()); // 10 — O(1), no remove 21 System.out.println("Peek tail: " + list.peekLast()); // 40 — O(1), no remove 22 23 // ── REMOVE ─────────────────────────────────────── 24 list.removeFirst(); // Remove head — O(1) 25 list.removeLast(); // Remove tail — O(1) 26 list.remove(Integer.valueOf(25)); // Remove by value — O(n) 27 System.out.println("After removes: " + list); // [20, 30] 28 29 // ── POLL (remove + return, null if empty) ──────── 30 LinkedList<Integer> q = new LinkedList<>(); 31 q.add(1); q.add(2); q.add(3); 32 System.out.println("Poll: " + q.poll()); // 1 — O(1), safe if empty 33 System.out.println("Queue: " + q); // [2, 3] 34 35 // ── SEARCH ─────────────────────────────────────── 36 LinkedList<String> names = new LinkedList<>(); 37 names.add("Alice"); names.add("Bob"); names.add("Alice"); 38 39 System.out.println("Contains Bob: " + names.contains("Bob")); // true 40 System.out.println("Index of Alice: " + names.indexOf("Alice")); // 0 41 System.out.println("Last Alice: " + names.lastIndexOf("Alice")); // 2 42 System.out.println("Size: " + names.size()); // 3 43 } 44}
Output (Java):
After adds:    [10, 20, 25, 30, 40]
First:         10
Last:          40
Index 2:       25
After removes: [20, 30]
Poll:          1
Contains Bob:  true

Java LinkedList as Stack and Queue

Java
1import java.util.LinkedList; 2import java.util.Deque; 3 4public class StackAndQueue { 5 6 public static void main(String[] args) { 7 // ── AS STACK (LIFO) ────────────────────────────── 8 Deque<Integer> stack = new LinkedList<>(); 9 stack.push(10); // push = addFirst — O(1) 10 stack.push(20); 11 stack.push(30); 12 System.out.println("Stack top: " + stack.peek()); // 30 — O(1) 13 System.out.println("Pop: " + stack.pop()); // 30 — O(1) 14 System.out.println("Stack: " + stack); // [20, 10] 15 16 // ── AS QUEUE (FIFO) ────────────────────────────── 17 Deque<Integer> queue = new LinkedList<>(); 18 queue.offer(10); // offer = addLast — O(1) 19 queue.offer(20); 20 queue.offer(30); 21 System.out.println("Queue front: " + queue.peek()); // 10 — O(1) 22 System.out.println("Dequeue: " + queue.poll()); // 10 — O(1) 23 System.out.println("Queue: " + queue); // [20, 30] 24 25 // ── AS DEQUE (DOUBLE-ENDED) ────────────────────── 26 Deque<Integer> deque = new LinkedList<>(); 27 deque.addFirst(20); 28 deque.addFirst(10); 29 deque.addLast(30); 30 deque.addLast(40); 31 System.out.println("Deque: " + deque); // [10, 20, 30, 40] 32 System.out.println("RemoveFirst: " + deque.removeFirst()); // 10 33 System.out.println("RemoveLast: " + deque.removeLast()); // 40 34 System.out.println("Deque: " + deque); // [20, 30] 35 } 36}
Output (Java):
Stack top: 30
Pop:       30
Stack:     [20, 10]
Queue front: 10
Dequeue:     10
Queue:       [20, 30]

Java: ArrayDeque<E> — Usually Preferred Over LinkedList

ArrayDeque is a resizable array-backed deque. For stack and queue use cases, it outperforms LinkedList in practice because it avoids per-node memory allocation and has better cache locality.

LinkedList<E>:
  Memory: ~48 bytes per node (prev pointer, next pointer, data, object header)
  Cache: poor — nodes scattered across heap
  Use when: frequent mid-list insertion/deletion with held iterators

ArrayDeque<E>:
  Memory: array of references, no per-node overhead
  Cache: excellent — elements stored contiguously
  Use when: stack or queue operations (push/pop/offer/poll at ends only)

Rule of thumb:
  Need a Queue?  → ArrayDeque (faster, less memory)
  Need a Stack?  → ArrayDeque (Java docs explicitly recommend it over Stack)
  Need index access or mid-list insert?  → LinkedList or ArrayList
Java
1import java.util.ArrayDeque; 2import java.util.Deque; 3 4public class ArrayDequeExample { 5 6 public static void main(String[] args) { 7 Deque<Integer> dq = new ArrayDeque<>(); 8 9 // Same API as LinkedList for deque operations 10 dq.addFirst(20); 11 dq.addFirst(10); 12 dq.addLast(30); 13 dq.addLast(40); 14 15 System.out.println("ArrayDeque: " + dq); // [10, 20, 30, 40] 16 System.out.println("PeekFirst: " + dq.peekFirst()); // 10 17 System.out.println("PeekLast: " + dq.peekLast()); // 40 18 19 dq.pollFirst(); 20 dq.pollLast(); 21 System.out.println("After poll: " + dq); // [20, 30] 22 23 // As a stack — Java docs recommend ArrayDeque over Stack class 24 Deque<String> stack = new ArrayDeque<>(); 25 stack.push("A"); 26 stack.push("B"); 27 stack.push("C"); 28 System.out.println("Stack: " + stack); // [C, B, A] 29 System.out.println("Pop: " + stack.pop()); // C 30 } 31}

C++: std::list vs std::forward_list

C++ provides two linked list types with different memory tradeoffs:

std::list<T>          — doubly linked list
  Nodes: prev + data + next  (~24 bytes per node on 64-bit)
  Bidirectional iterators
  Operations: push/pop front and back, insert/erase at iterator
  size(): O(1)

std::forward_list<T>  — singly linked list (C++11)
  Nodes: data + next  (~16 bytes per node on 64-bit)
  Forward iterators only
  Operations: push/pop front only, insert_after/erase_after
  size(): NOT PROVIDED — use std::distance (O(n))
  Use when: memory is critical and backward traversal not needed
Java
1// Java equivalent: LinkedList (doubly) vs custom singly linked list 2// Java has no built-in singly linked list — LinkedList is always doubly 3import java.util.LinkedList; 4 5public class ListComparison { 6 public static void main(String[] args) { 7 // Java LinkedList — doubly linked, bidirectional 8 LinkedList<Integer> doubly = new LinkedList<>(); 9 doubly.add(10); doubly.add(20); doubly.add(30); 10 11 // Iterate forward 12 System.out.print("Forward: "); 13 for (int x : doubly) System.out.print(x + " "); 14 System.out.println(); 15 16 // Iterate backward using descendingIterator 17 System.out.print("Backward: "); 18 var it = doubly.descendingIterator(); 19 while (it.hasNext()) System.out.print(it.next() + " "); 20 System.out.println(); 21 } 22}

Complete API Comparison Table

OperationJava LinkedListPython dequeC++ std::listC++ forward_list
Add to frontaddFirst(e) O(1)appendleft(e) O(1)push_front(e) O(1)push_front(e) O(1)
Add to backaddLast(e) O(1)append(e) O(1)push_back(e) O(1)❌ Not available
Add at indexadd(i, e) O(n)❌ O(n) workaroundinsert(it, e) O(1)*insert_after(it,e) O(1)*
Peek frontpeekFirst() O(1)d[0] O(1)front() O(1)front() O(1)
Peek backpeekLast() O(1)d[-1] O(1)back() O(1)❌ Not available
Remove frontremoveFirst() O(1)popleft() O(1)pop_front() O(1)pop_front() O(1)
Remove backremoveLast() O(1)pop() O(1)pop_back() O(1)❌ Not available
Remove by valueremove(o) O(n)remove(v) O(n)remove(v) O(n)remove(v) O(n)
Sizesize() O(1)len(d) O(1)size() O(1)❌ O(n) via distance
Containscontains(o) O(n)v in d O(n)find(...) O(n)find(...) O(n)
Random accessget(i) O(n)d[i] O(n) middle❌ No operator[]❌ No operator[]
Reverse iteratedescendingIterator()reversed(d)rbegin/rend❌ Not available

*O(1) given an iterator already at position — O(n) to find the position.

When to Use Each

Java:
  LinkedList  → when you need mid-list insertion with held iterators,
                or implementing LRU cache (uses both List and Deque ops)
  ArrayDeque  → stack, queue, or deque use only (faster than LinkedList)
  ArrayList   → when random access is frequent (most other cases)

Python:
  deque       → any stack, queue, or sliding window needing O(1) both ends
  list        → general use with random access
  Never use list.pop(0) or list.insert(0, x) in a loop — O(n) each

C++:
  std::list        → bidirectional traversal, frequent mid-insert with iterators
  std::forward_list → memory-critical singly linked, no backward traversal needed
  std::deque       → O(1) both ends AND O(1) random access (array-of-blocks)
  std::vector      → general purpose with random access (most other cases)

JavaScript:
  Array             → all general use cases (no built-in linked list)
  Custom Node class → interview problems specifically about linked list structure
  Map-based Deque   → O(1) front and back operations without O(n) array shifting

Common Mistakes Beginners Make

Using LinkedList as a queue with remove() instead of poll() in Java. remove() throws NoSuchElementException if the list is empty. poll() returns null. In production code, always use poll() for safe dequeue — it does not require a separate empty check.

Using Python list with pop(0) or insert(0, x) in a loop. These are O(n) operations. A loop of n iterations where each calls pop(0) gives O(n²) total. Use collections.deque for O(1) popleft() and appendleft().

Using std::forward_list::size() — it does not exist. forward_list has no size() method. You must use std::distance(fl.begin(), fl.end()) which is O(n), or track the count manually.

Using list.get(i) in Java inside a loop. LinkedList.get(i) is O(n) — iterating n times over indices with get() gives O(n²). Use an iterator (for (int x : list)) for O(n) traversal.

Preferring LinkedList over ArrayDeque for Java queue/stack. ArrayDeque is faster, uses less memory, and the Java documentation explicitly recommends it over LinkedList for stack and queue use cases.

Interview Questions

Q: When would you use LinkedList instead of ArrayList in Java?

LinkedList when: (1) you frequently insert or delete from the middle while already holding an iterator (O(1) with iterator vs O(n) for ArrayList), (2) you need O(1) operations at both ends as a deque, or (3) you are implementing a data structure like LRU cache that requires both list traversal and deque operations. ArrayList is preferred for almost everything else — random access is O(1) vs O(n) and cache locality is far better.

Q: Why is collections.deque preferred over list for queue operations in Python?

Python's built-in list uses a dynamic array internally. list.pop(0) (dequeue from front) and list.insert(0, x) (enqueue at front) are both O(n) — every element shifts. collections.deque is implemented as a doubly linked list of fixed-size blocks, giving O(1) popleft() and appendleft(). For n dequeue operations in a loop: list.pop(0) gives O(n²); deque.popleft() gives O(n).

Q: What is the difference between std::list and std::forward_list in C++?

std::list is a doubly linked list — each node has prev and next pointers, enabling bidirectional iteration, push_back, pop_back, and back(). std::forward_list is a singly linked list — each node has only next, enabling only forward iteration and front operations. forward_list uses about 33% less memory per node and is faster for simple forward traversal. It sacrifices push_back, back(), size(), and reverse iteration. Use forward_list when memory is critical and backward traversal is never needed.

FAQs

Does Java's LinkedList implement both List and Deque?

Yes. LinkedList<E> implements List<E>, Deque<E>, and Queue<E>. This makes it uniquely versatile — you can use it for index-based list operations (get(i), add(i, e)), queue operations (offer, poll, peek), stack operations (push, pop), and deque operations (addFirst, addLast, removeFirst, removeLast). The downside is that List operations like get(i) are O(n), which traps developers who assume array-like random access.

Is Python's deque actually a linked list?

Not exactly. CPython's deque is implemented as a doubly linked list of fixed-size blocks (each holding 64 elements). This gives O(1) append and popleft with better cache performance than a classic node-per-element linked list. Accessing elements by index (d[i]) is O(n) for middle elements but O(1) for the first and last — consistent with linked list semantics.

Why doesn't JavaScript have a built-in linked list?

JavaScript was designed primarily for the browser where DOM manipulation and event handling dominate. A doubly linked list was not considered essential enough for the core library. The built-in Array covers most use cases. For interview problems involving linked list structure (reversing, cycle detection, merging), implement a ListNode class from scratch — that is the expected approach in JavaScript interviews.

Quick Quiz

Question 1: In Java, which class is recommended for use as a stack or queue instead of LinkedList?

  • A) ArrayList
  • B) Stack
  • C) ArrayDeque
  • D) PriorityQueue

Answer: C) ArrayDeque. Java's own documentation recommends ArrayDeque over LinkedList for stack and queue use. It is faster (no per-node allocation, better cache locality) and uses less memory. Stack is a legacy class extending Vector — avoid it.

Question 2: What is the time complexity of Python list.pop(0) vs collections.deque.popleft()?

  • A) Both O(1)
  • B) list.pop(0) is O(n), deque.popleft() is O(1)
  • C) Both O(n)
  • D) list.pop(0) is O(1), deque.popleft() is O(n)

Answer: B) list.pop(0) is O(n), deque.popleft() is O(1). Python list is a dynamic array — removing from index 0 shifts all remaining elements left, costing O(n). deque is a linked list of blocks — popleft simply updates a pointer, costing O(1).

Question 3: Which C++ container provides O(1) random access AND O(1) push/pop at both ends?

  • A) std::list
  • B) std::forward_list
  • C) std::deque
  • D) std::vector

Answer: C) std::deque. std::deque is an array-of-blocks structure giving O(1) amortized push/pop at front and back, plus O(1) random access via operator[]. std::list has no O(1) random access. std::vector has O(n) push/pop at front.

Question 4: In Java's LinkedList, what is the complexity of get(index)?

  • A) O(1) — same as ArrayList
  • B) O(log n) — binary search on nodes
  • C) O(n) — must traverse from head or tail to the index
  • D) O(1) amortized — cached after first access

Answer: C) O(n). LinkedList.get(i) starts from head (or tail if i > size/2) and follows next (or prev) pointers until reaching index i. For index n/2 (middle), it takes n/2 steps. Using get(i) in a loop over n indices results in O(n²) — always use an iterator for O(n) full traversal.

Summary

Every major language provides built-in linked list implementations. The choice between them depends on whether you need bidirectional traversal, random access, thread safety, or minimal memory per node.

Key decisions to carry forward:

  • Java — use ArrayDeque for stack/queue (faster), LinkedList for mid-list insert with iterators or LRU cache
  • Python — always use collections.deque for queue/stack; never use list.pop(0) in a loop (O(n²))
  • C++std::list for bidirectional doubly linked, std::forward_list for memory-critical singly linked, std::deque for O(1) both ends + random access
  • JavaScript — no built-in; use Array (aware of O(n) shift/unshift) or implement ListNode class for interview problems
  • LinkedList.get(i) in Java is O(n) — never call it in a loop; use iterators
  • std::forward_list has no size(), no push_back(), no back(), no reverse iterator
  • collections.deque supports a maxlen parameter for bounded circular buffers — no other language has this built in

In the next topic, you will explore Traversal and Searching — the patterns for walking linked lists, finding elements, and the two-pointer techniques that dominate linked list interview problems.

Linked List Collection Frameworks