Chain of nodes connected by pointers, enabling O(1) insertions and deletions at known positions without shifting elements.
Linear
Linked List Fundamentals
A linked list stores elements as a chain of nodes, where each node holds a value and a pointer to the next node. Unlike arrays, elements are scattered across memory โ but any node can be inserted or removed in O(1) time once you have a reference to its neighbour, making linked lists the natural complement to arrays.
01
Section One ยท Foundation
What is Linked List?
A linked list is a collection of nodes allocated anywhere in memory, each holding two things โ a data value and a pointer (reference) to the next node. Because nodes are not contiguous, there is no address formula for direct access: reaching element i requires following pointers from the head node, one hop at a time. This makes access O(n) but makes insertion and deletion at a known position O(1) โ no shifting required, just pointer rewiring.
Linked lists come in two main flavours:
Singly Linked
One Direction
Each node stores one pointer โ next. Traversal is forward-only. Memory-efficient: one pointer overhead per node.
Simple to implement
Efficient for stack and queue internals
Can only traverse forward
Doubly Linked
Bidirectional
Each node stores two pointers โ next and prev. Enables backward traversal and O(1) deletion without needing the predecessor.
O(1) delete with just a node reference
Enables deque (double-ended queue)
2ร pointer memory overhead per node
Analogy: A treasure hunt where each clue tells you only where the next clue is hidden. You cannot jump to clue number 7 โ you must start at clue 1 and follow every step in sequence. But adding a new clue anywhere in the chain is instant: rewrite two clues to point through the new one.
Key Characteristics
Nodes scattered in heap memory
โถ
No O(1) index access โ must traverse
Each node holds a next pointer
โถ
Insert/delete O(1) with a reference in hand
No pre-allocation needed
โถ
Size grows/shrinks dynamically with zero waste
No contiguous layout
โถ
Cache-unfriendly โ slower iteration than arrays
Doubly linked adds prev pointer
โถ
O(1) delete & backward traversal
02
Section Two ยท Operations
Core Operations
Traverse
Visits every node from head to tail by following next pointers, accumulating or searching as it goes.
Pseudocode
functiontraverse(head):
current = head
while current is not null:
visit(current.data) // O(1) per node
current = current.next // advance pointer// total: O(n)
Always check for null before accessing .next. The most common linked list crash is dereferencing a null pointer at the tail. Every loop condition should be current != null โ not current.next != null โ unless you specifically need to stop one node before the end.
Insert Node
Wires a new node into the chain at the head, tail, or a given position by updating exactly two pointers.
Pseudocode
functioninsertAfter(prevNode, value):
newNode = Node(value)
newNode.next = prevNode.next // step 1: point new โ successor
prevNode.next = newNode // step 2: point prev โ new// O(1) โ only two pointer updatesfunctioninsertAtHead(head, value):
newNode = Node(value)
newNode.next = head // new node points to old head
head = newNode // head now points to new nodereturn head
Order the pointer updates carefully. When inserting, always set newNode.next first, then update the predecessor's pointer. Reversing this order loses the reference to the rest of the list โ a very common bug.
Delete Node
Removes a node from the chain by bypassing it โ redirecting the predecessor's next pointer to skip over the target node.
Pseudocode
functiondeleteAfter(prevNode):
if prevNode.next is null:
return// nothing to delete
prevNode.next = prevNode.next.next // bypass target// O(1) with prevNode in handfunctiondeleteByValue(head, target):
if head.data == target:
return head.next // delete head โ O(1)
current = head
while current.next is not null:
if current.next.data == target:
current.next = current.next.next // bypassreturn head
current = current.next
return head // not found โ O(n) search
Finding the node to delete is O(n); the deletion itself is O(1). In a doubly linked list, deletion given only the node reference (no predecessor) is O(1) because node.prev gives you the predecessor directly. In a singly linked list, you must traverse to find it โ O(n).
Cycle Detection โ Floyd's Algorithm
Uses a slow pointer advancing one step and a fast pointer advancing two steps to detect whether the list contains a cycle in O(n) time and O(1) space.
Pseudocode
functionhasCycle(head):
slow = head
fast = head
while fast is not nulland fast.next is not null:
slow = slow.next // advance 1 step
fast = fast.next.next // advance 2 stepsif slow == fast:
return true// pointers met โ cycle existsreturn false// fast hit null โ no cycle
If there is a cycle, slow and fast will always meet. Fast gains one step on slow per iteration. The gap between them decreases by 1 each time, so they are guaranteed to converge inside the cycle โ never to "jump over" each other. This is also the basis of finding the cycle entry point (LeetCode 142).
Common Mistake: Losing the head reference. When you write head = head.next to advance, the original head is gone forever โ the entire list behind it is orphaned. Always use a separate current pointer for traversal and leave head pointing to the first node at all times.
03
Section Three ยท Visuals
Diagrams & Memory Layout
Singly vs Doubly Linked List Structure
Node anatomy โ value compartment + pointer compartment(s)
Insert After Node โ Pointer Rewiring
Inserting node 99 after node 20 โ two pointer updates
Floyd's Cycle Detection โ Slow & Fast Pointers
Slow pointer (1 step) and fast pointer (2 steps) converge inside the cycle
04
Section Four ยท Code
Java Quick Reference
The most common linked list operations using Java's built-in LinkedList โ a doubly linked list you'll use in real code and interviews.
Java โ LinkedList Core Operations
import java.util.*;
LinkedList<Integer> list = newLinkedList<>();
// O(1) โ insert at head and tail
list.addFirst(10); // [10]
list.addLast(30); // [10, 30]
list.add(1, 20); // O(n) insert at index โ [10, 20, 30]// O(n) โ access by index (must traverse)int val = list.get(1); // 20int head = list.getFirst(); // 10 โ O(1)int tail = list.getLast(); // 30 โ O(1)// O(1) โ remove from head
list.removeFirst(); // removes 10 โ [20, 30]// O(n) โ remove by index
list.remove(1); // removes 30 โ [20]// O(n) โ searchboolean has = list.contains(20); // trueint idx = list.indexOf(20); // 0
Linked List โ Full Implementation
Java โ SinglyLinkedList<T> from scratch
public classSinglyLinkedList<T> {
static classNode<T> {
T data;
Node<T> next;
Node(T data) { this.data = data; }
}
privateNode<T> head, tail;
private int size;
// โโ O(1) โ add at head โโโโโโโโโโโโโโโโโโโโโโโโโโโpublic voidaddFirst(T val) {
Node<T> n = newNode<>(val);
n.next = head;
head = n;
if (tail == null) tail = n;
size++;
}
// โโ O(1) โ add at tail โโโโโโโโโโโโโโโโโโโโโโโโโโโpublic voidaddLast(T val) {
Node<T> n = newNode<>(val);
if (tail != null) tail.next = n;
tail = n;
if (head == null) head = n;
size++;
}
// โโ O(n) โ add at index โโโโโโโโโโโโโโโโโโโโโโโโโโpublic voidaddAt(int index, T val) {
if (index < 0 || index > size)
throw newIndexOutOfBoundsException();
if (index == 0) { addFirst(val); return; }
if (index == size) { addLast(val); return; }
Node<T> prev = head;
for (int i = 0; i < index - 1; i++) prev = prev.next;
Node<T> n = newNode<>(val);
n.next = prev.next;
prev.next = n;
size++;
}
// โโ O(1) โ remove head โโโโโโโโโโโโโโโโโโโโโโโโโโpublicTremoveFirst() {
if (head == null) throw newNoSuchElementException();
T val = head.data;
head = head.next;
if (head == null) tail = null;
size--;
return val;
}
// โโ O(n) โ remove tail (singly: must find second-to-last) โโpublicTremoveLast() {
if (head == null) throw newNoSuchElementException();
if (head == tail) returnremoveFirst();
Node<T> cur = head;
while (cur.next != tail) cur = cur.next;
T val = tail.data;
tail = cur;
tail.next = null;
size--;
return val;
}
// โโ O(n) โ remove at index โโโโโโโโโโโโโโโโโโโโโโโpublicTremoveAt(int index) {
if (index < 0 || index >= size)
throw newIndexOutOfBoundsException();
if (index == 0) returnremoveFirst();
Node<T> prev = head;
for (int i = 0; i < index - 1; i++) prev = prev.next;
T val = prev.next.data;
prev.next = prev.next.next;
if (index == size - 1) tail = prev;
size--;
return val;
}
// โโ O(n) โ get by index โโโโโโโโโโโโโโโโโโโโโโโโโpublicTget(int index) {
if (index < 0 || index >= size)
throw newIndexOutOfBoundsException();
Node<T> cur = head;
for (int i = 0; i < index; i++) cur = cur.next;
return cur.data;
}
// โโ O(n) โ search โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโpublic intindexOf(T val) {
Node<T> cur = head;
for (int i = 0; i < size; i++) {
if (cur.data.equals(val)) return i;
cur = cur.next;
}
return -1;
}
// โโ O(n) โ iterative reverse (3-pointer) โโโโโโโโpublic voidreverse() {
tail = head;
Node<T> prev = null, cur = head, next;
while (cur != null) {
next = cur.next;
cur.next = prev;
prev = cur;
cur = next;
}
head = prev;
}
// โโ O(n) โ Floyd's cycle detection โโโโโโโโโโโโโโpublic booleanhasCycle() {
Node<T> slow = head, fast = head;
while (fast != null && fast.next != null) {
slow = slow.next;
fast = fast.next.next;
if (slow == fast) return true;
}
return false;
}
public intsize() { return size; }
publicStringtoString() {
StringBuilder sb = newStringBuilder();
Node<T> cur = head;
while (cur != null) {
sb.append(cur.data);
if (cur.next != null) sb.append(" โ ");
cur = cur.next;
}
sb.append(" โ null");
return sb.toString();
}
public static voidmain(String[] args) {
SinglyLinkedList<Integer> ll = newSinglyLinkedList<>();
ll.addLast(10); ll.addLast(20);
ll.addLast(30); ll.addFirst(5);
System.out.println(ll); // 5 โ 10 โ 20 โ 30 โ null
ll.addAt(2, 15);
System.out.println(ll); // 5 โ 10 โ 15 โ 20 โ 30 โ null
ll.removeFirst();
ll.removeLast();
System.out.println(ll); // 10 โ 15 โ 20 โ null
ll.reverse();
System.out.println(ll); // 20 โ 15 โ 10 โ nullSystem.out.println(ll.indexOf(15)); // 1System.out.println(ll.hasCycle()); // false
}
}
Python โ deque operations & manual Node class
from collections import deque
# โโโ 1. Built-in deque (doubly linked under the hood) โโโโโโโโโ
d = deque([10, 20, 30])
d.appendleft(5) # O(1) โ [5, 10, 20, 30]
d.append(40) # O(1) โ [5, 10, 20, 30, 40]
d.popleft() # O(1) โ removes 5
d.pop() # O(1) โ removes 40
d.rotate(1) # O(k) โ move last to front# โโโ 2. Manual singly linked list โโโโโโโโโโโโโโโโโโโโโโโโโโโโโclassNode:
def__init__(self, data, next=None):
self.data = data
self.next = nextclassLinkedList:
def__init__(self):
self.head = None
self.size = 0defprepend(self, val): # O(1)
self.head = Node(val, self.head)
self.size += 1defappend(self, val): # O(n) without tailif not self.head:
self.head = Node(val)
else:
cur = self.head
while cur.next: cur = cur.next
cur.next = Node(val)
self.size += 1defdelete_value(self, val): # O(n)if not self.head: returnif self.head.data == val:
self.head = self.head.next
self.size -= 1; return
cur = self.head
while cur.next:
if cur.next.data == val:
cur.next = cur.next.next
self.size -= 1; return
cur = cur.next
# โโโ 3. Iterative reversal โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโdefreverse(head):
prev, cur = None, head
while cur:
nxt = cur.next
cur.next = prev
prev = cur
cur = nxt
return prev # new head# โโโ 4. Floyd's cycle detection โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโdefhas_cycle(head):
slow = fast = head
while fast and fast.next:
slow = slow.next
fast = fast.next.next
if slow == fast:
return Truereturn False
JavaScript โ LinkedList class (ES6)
classNode {
constructor(val) {
this.val = val;
this.next = null;
}
}
classLinkedList {
constructor() {
this.head = null;
this.tail = null;
this.size = 0;
}
// O(1) โ add to frontprepend(val) {
const n = newNode(val);
n.next = this.head;
this.head = n;
if (!this.tail) this.tail = n;
this.size++;
}
// O(1) โ add to backappend(val) {
const n = newNode(val);
if (this.tail) this.tail.next = n;
this.tail = n;
if (!this.head) this.head = n;
this.size++;
}
// O(n) โ insert at indexinsertAt(index, val) {
if (index === 0) returnthis.prepend(val);
if (index === this.size) returnthis.append(val);
let prev = this.head;
for (let i = 0; i < index - 1; i++) prev = prev.next;
const n = newNode(val);
n.next = prev.next;
prev.next = n;
this.size++;
}
// O(1) โ remove headremoveHead() {
if (!this.head) return null;
const val = this.head.val;
this.head = this.head.next;
if (!this.head) this.tail = null;
this.size--;
return val;
}
// O(n) โ remove tailremoveTail() {
if (!this.head) return null;
if (this.head === this.tail) returnthis.removeHead();
let cur = this.head;
while (cur.next !== this.tail) cur = cur.next;
const val = this.tail.val;
this.tail = cur;
this.tail.next = null;
this.size--;
return val;
}
// O(n) โ find by valuefind(val) {
let cur = this.head;
while (cur) {
if (cur.val === val) return cur;
cur = cur.next;
}
return null;
}
// O(n) โ iterative reversereverse() {
this.tail = this.head;
let prev = null, cur = this.head;
while (cur) {
const next = cur.next;
cur.next = prev;
prev = cur;
cur = next;
}
this.head = prev;
}
// O(n) โ Floyd's cycle detectionhasCycle() {
let slow = this.head, fast = this.head;
while (fast && fast.next) {
slow = slow.next;
fast = fast.next.next;
if (slow === fast) return true;
}
return false;
}
toString() {
const parts = [];
let cur = this.head;
while (cur) { parts.push(cur.val); cur = cur.next; }
return parts.join(' -> ') + ' -> null';
}
}
using System;
using System.Collections.Generic;
// โโโ 1. Built-in LinkedList<T> (doubly linked) โโโโโโโโโโโโโโvar list = newLinkedList<int>();
list.AddFirst(10); // O(1)
list.AddLast(30); // O(1)var node20 = list.AddAfter(list.First, 20); // O(1) with node ref
list.Remove(node20); // O(1) โ direct node removal
list.RemoveFirst(); // O(1)bool has = list.Contains(30); // O(n)var found = list.Find(30); // O(n) โ returns LinkedListNode// โโโ 2. Manual singly linked list โโโโโโโโโโโโโโโโโโโโโโโโโโโโpublic classSinglyLinkedList<T> {
classNode {
publicT Data;
publicNode Next;
publicNode(T data) { Data = data; }
}
privateNode _head, _tail;
private int _size;
public voidAddFirst(T val) { // O(1)var n = newNode(val) { Next = _head };
_head = n;
_tail ??= n;
_size++;
}
public voidAddLast(T val) { // O(1)var n = newNode(val);
if (_tail != null) _tail.Next = n;
_tail = n;
_head ??= n;
_size++;
}
publicTRemoveFirst() { // O(1)if (_head == null) throw newInvalidOperationException();
T val = _head.Data;
_head = _head.Next;
if (_head == null) _tail = null;
_size--;
return val;
}
public voidReverse() { // O(n)
_tail = _head;
Node prev = null, cur = _head;
while (cur != null) {
var next = cur.Next;
cur.Next = prev;
prev = cur;
cur = next;
}
_head = prev;
}
publicNodeFind(T val) { // O(n)var cur = _head;
while (cur != null) {
if (cur.Data.Equals(val)) return cur;
cur = cur.Next;
}
return null;
}
public int Count => _size;
}
05
Section Five ยท Complexity
Time & Space Complexity
Operation
Best Case
Average Case
Worst Case
Space
Access by index
O(1) at head
O(n)
O(n)
O(1)
Insert at head / tail
O(1)
O(1)
O(1)
O(1)
Insert at index
O(1)
O(n)
O(n)
O(1)
Delete at head
O(1)
O(1)
O(1)
O(1)
Delete at index / tail (singly)
O(1)
O(n)
O(n)
O(1)
Why these complexities? There is no address formula โ reaching index i requires following i pointers from the head. But once you hold a reference to a node's predecessor, the insert or delete is just two pointer assignments โ truly O(1) regardless of list length. Tail insertion is O(1) only when a tail pointer is maintained; without it, you must traverse the entire list to find the last node.
Space Complexity Note
A linked list of n nodes uses O(n) space for data, plus O(n) for pointers โ one per node for singly linked, two per node for doubly linked. This pointer overhead is the primary memory disadvantage versus arrays. However, there is no wasted capacity: a linked list of size n uses exactly n nodes, with no over-allocation. Auxiliary space for all operations is O(1) except recursion-based reversal which uses O(n) call-stack space.
06
Section Six ยท Decision Guide
When to Use Linked List
โ
Use This When
Frequent insertions / deletions at known positions โ O(1) pointer rewiring beats O(n) array shifts every time.
You're building a stack or queue โ addFirst / removeFirst (stack) and addLast / removeFirst (queue) are both O(1).
Size changes constantly โ no resizing, no capacity waste, memory is allocated and freed per node.
โ
Avoid When
Random access is frequent โ every get(i) is O(n); an array reaches the same element in O(1).
Cache performance matters โ nodes scattered in heap memory miss the CPU cache heavily; arrays iterate 3โ5ร faster in practice.
Comparison vs Alternatives
Structure
Access
Insert
Delete
Best For
Linked List โ this one
O(n)
O(1)
O(1)
Frequent inserts/deletes, stack/queue internals
Array
O(1)
O(n)
O(n)
Random access, cache-friendly iteration
Doubly Linked List
O(n)
O(1)
O(1) no traversal
Deque, LRU cache, browser history
07
Section Seven ยท Interview Practice
LeetCode Problems
Linked list interview problems almost always involve one of four patterns: two pointers (fast/slow for cycle detection and midpoint finding), dummy head node (simplifies edge cases at the list head), in-place reversal (rewiring pointers without extra memory), or merge patterns (combining two sorted lists by pointer comparison).
EasyReverse Linked List โ
Three-pointer iterative approach: prev, curr, next โ then swap curr.next = prev each step.
EasyMerge Two Sorted Lists โ
Use a dummy head node; compare heads and attach the smaller one each step โ clean O(n+m) merge.
MediumLinked List Cycle II โ
Floyd's detection finds the meeting point; then reset one pointer to head and advance both one step โ they meet at the cycle entry.
MediumRemove Nth Node From End โ
Two-pointer gap trick: advance the fast pointer n steps first, then move both โ fast hitting null means slow is at the target.
HardLRU Cache โ
Combine a HashMap with a doubly linked list โ O(1) get and put by using the map for lookup and the list for O(1) move-to-front.
Interview Pattern: When a linked list problem feels complicated, always ask two questions first: "Would a dummy head node eliminate my edge cases?" and "Can slow/fast pointers replace a two-pass traversal?" These two moves handle the majority of medium linked list problems without needing extra data structures.