Learn/DSA/Linked Lists
Data StructuresBeginner9 min

Linked Lists

Nodes chained by pointers: O(1) insert/delete where you hold a reference, but no random access.

Linked ListsPointersTwo Pointers

A linked list is a chain of little boxes. Each box — a node — holds a value and a pointer to the next box. There is no single contiguous block of memory; the nodes can live anywhere, and the next pointers are what stitch them into a sequence.

That design flips the array trade-off on its head. You lose instant indexing — to reach the 5th node you must walk from the head through four next hops. But once you are standing at a node, splicing a new node in or cutting one out is just a couple of pointer swaps, no shifting of everything after it.

Singly Linked List
head →
3
•
→
7
•
→
1
•
→
9
•
→ ∅
No index — you traverse from head via next pointers.
Insert and delete rewire a couple of pointers in O(1); reaching a position still costs an O(n) walk from the head.

Singly vs doubly

A singly linked list stores one pointer per node (next), so you can only travel forward. A doubly linked list adds a prev pointer, letting you walk backward and delete a node in O(1) when you only have a reference to it (no need to hunt for its predecessor). The cost is an extra pointer of memory per node.

Most implementations track a head reference; many also keep a tail so that appending is O(1) instead of an O(n) walk to the end. A doubly linked list with head and tail is the backbone of a deque.

OperationTime

The headline: constant-time structural edits, but no O(1) indexing and pointer overhead per element.

Reversing a list

Reversal is the "hello world" of pointer manipulation and a near-guaranteed interview question. Walk the list once, and at each node flip its next pointer to point backward. Keep three references — prev, curr, and a saved next — so you never lose the rest of the chain.

Reverse a singly linked list — O(n) time, O(1) space
function reverse(head) {
  let prev = null
  let curr = head
  while (curr) {
    const next = curr.next // save before we overwrite
    curr.next = prev       // flip the pointer
    prev = curr            // advance both
    curr = next
  }
  return prev // new head
}

Cycle detection — Floyd’s algorithm

What if a next pointer loops back into the list? A naive scan would run forever. Floyd’s tortoise and hare sends two pointers through the list at different speeds: slow moves one step, fast moves two. If there is a cycle, the fast pointer eventually laps the slow one and they meet — all in O(n) time and O(1) space, no extra hash set required.

Detect a cycle with two pointers — O(n) time, O(1) space
function hasCycle(head) {
  let slow = head, fast = head
  while (fast && fast.next) {
    slow = slow.next
    fast = fast.next.next
    if (slow === fast) return true // they met -> cycle
  }
  return false // fast fell off the end -> no cycle
}

Interview reflex

Reach for a dummy head node when a problem might delete or insert at the front — it kills the "what if head changes?" edge case. And whenever you hear "detect a loop" or "find the middle in one pass," think fast and slow pointers.

When to reach for a linked list

  • You do frequent inserts/deletes at the ends or at held references — O(1) splices.
  • You are building a queue, stack, or deque and want guaranteed O(1) edge operations with no resizing.
  • You need a stable structure where node references stay valid even as the list grows.
  • Avoid them when you need random access or tight, cache-friendly iteration — an array wins on both.

Where you’ll see it

A doubly linked list plus a hash map is exactly how an LRU cache works: the map gives O(1) lookup, and the list keeps items in recency order so the least-recently-used node can be evicted from the tail in O(1). Next up: stacks — a linked list or array restricted to one end.

Section navigation