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
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
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
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
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
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
| Operation | Java LinkedList | Python deque | C++ std::list | C++ forward_list |
|---|---|---|---|---|
| Add to front | addFirst(e) O(1) | appendleft(e) O(1) | push_front(e) O(1) | push_front(e) O(1) |
| Add to back | addLast(e) O(1) | append(e) O(1) | push_back(e) O(1) | ❌ Not available |
| Add at index | add(i, e) O(n) | ❌ O(n) workaround | insert(it, e) O(1)* | insert_after(it,e) O(1)* |
| Peek front | peekFirst() O(1) | d[0] O(1) | front() O(1) | front() O(1) |
| Peek back | peekLast() O(1) | d[-1] O(1) | back() O(1) | ❌ Not available |
| Remove front | removeFirst() O(1) | popleft() O(1) | pop_front() O(1) | pop_front() O(1) |
| Remove back | removeLast() O(1) | pop() O(1) | pop_back() O(1) | ❌ Not available |
| Remove by value | remove(o) O(n) | remove(v) O(n) | remove(v) O(n) | remove(v) O(n) |
| Size | size() O(1) | len(d) O(1) | size() O(1) | ❌ O(n) via distance |
| Contains | contains(o) O(n) | v in d O(n) | find(...) O(n) | find(...) O(n) |
| Random access | get(i) O(n) | d[i] O(n) middle | ❌ No operator[] | ❌ No operator[] |
| Reverse iterate | descendingIterator() | 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
ArrayDequefor stack/queue (faster),LinkedListfor mid-list insert with iterators or LRU cache - ›Python — always use
collections.dequefor queue/stack; never uselist.pop(0)in a loop (O(n²)) - ›C++ —
std::listfor bidirectional doubly linked,std::forward_listfor memory-critical singly linked,std::dequefor O(1) both ends + random access - ›JavaScript — no built-in; use
Array(aware of O(n) shift/unshift) or implementListNodeclass for interview problems - ›
LinkedList.get(i)in Java is O(n) — never call it in a loop; use iterators - ›
std::forward_listhas nosize(), nopush_back(), noback(), no reverse iterator - ›
collections.dequesupports amaxlenparameter 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.