Skip to content

Beginner: foundations · Stacks, queues and linked lists · topic 19 of 61 · 40 min

Linked lists

Explanation

A linked list is a chain of nodes, each holding a value and a pointer to the next node. Adding or removing at the head is O(1), but there is no random access: reaching position i means walking i steps. Singly linked lists point forwards only; doubly linked lists also point back. Reversing a list is the classic exercise: three pointers iteratively, or recursion with O(n) stack space.

Nodes and pointers

A linked list is a chain of nodes. Each node holds a value and a reference, or pointer, to the next node. The first node is the head. The last node's next is None, which marks the end. Unlike an array, the nodes do not sit side by side in memory; each node only knows where the next one is.

Think of it like this: A treasure hunt: each clue tells you where the next clue is hidden. To reach clue 7 you must follow clues 1 to 6. But adding a new first clue is easy: write a clue that points to the old first one and hand it out.
python
class ListNode:
    def __init__(self, val=0, next=None):
        self.val = val
        self.next = next

head = ListNode(1, ListNode(2, ListNode(3)))   # 1 -> 2 -> 3
node = head
while node:
    print(node.val, end=" ")
    node = node.next
# Output: 1 2 3
Walking a list: follow next until it is None. O(n) time.

Singly and doubly linked

In a singly linked list each node points only to the next one, so you can walk forwards only. A doubly linked list also stores prev, a pointer to the node before, so you can walk both ways and unlink a node in O(1) once you hold a reference to it, at the cost of one extra pointer per node. LRU caches (a hash map plus a doubly linked list) and music playlists with "previous" use doubly linked lists. Java's LinkedList is doubly linked, and CPython's deque is a doubly linked list of blocks.

What each operation costs

OperationArray (Python list)Singly linked list
Read the item at position iO(1)O(i): walk from the head
Insert or delete at the frontO(n): shift everythingO(1)
Insert after a node you already holdO(n): shift the restO(1)
Insert at position iO(n)O(i) to walk there, then O(1)
Append at the endO(1) amortisedO(n), or O(1) with a tail pointer
Search for a valueO(n)O(n)
Extra memory per itemnoneone pointer (two if doubly linked)

There is no random access: you cannot jump to the fifth node, you must walk there. In practice linked lists are also slower to scan than arrays, because their nodes are scattered in memory and the processor's cache helps less.

Insert and delete

Insert at the head: make a new node whose next is the old head; it becomes the new head. Delete the head: head = head.next. To insert at position i, walk to the node just before it, prev, and splice: new.next = prev.next, then prev.next = new. The order of those two steps matters: set prev.next first and the rest of the list is lost.

python
head = ListNode(2, ListNode(3))       # 2 -> 3
head = ListNode(1, head)              # insert at the head: 1 -> 2 -> 3
prev = head.next                      # the node holding 2
prev.next = ListNode(9, prev.next)    # insert after it: 1 -> 2 -> 9 -> 3
head = head.next                      # delete the head: 2 -> 9 -> 3

The dummy node trick

Deleting is awkward when the head itself might be deleted, because then the head changes. A dummy (or sentinel) node placed before the head removes that special case: every real node now has a node before it. Do the work starting from the dummy and return dummy.next at the end.

Reversing a list iteratively

Reversing turns every arrow around. Walk with three pointers: prev (the part already reversed, starting as None), cur (the node being turned) and nxt (saved first so the rest of the list is not lost). O(n) time and O(1) extra space.

python
def reverse(head):
    prev, cur = None, head
    while cur:
        nxt = cur.next      # 1. save the rest
        cur.next = prev     # 2. turn this arrow around
        prev = cur          # 3. move prev forward
        cur = nxt           # 4. move cur forward
    return prev             # the old tail is the new head
After stepprev (reversed part)cur (rest)
startNone1 → 2 → 3
1st loop12 → 3
2nd loop2 → 13
3rd loop3 → 2 → 1None

Reversing recursively

Recursively: reverse everything after the head, then hook the head on at the end. The recursion goes n calls deep, so it uses O(n) stack space, and CPython stops at about 1,000 nested calls by default with a RecursionError. Prefer the iterative version for long lists; recursion is covered in depth in Module 5.

python
def reverse_rec(head):
    if head is None or head.next is None:
        return head
    new_head = reverse_rec(head.next)
    head.next.next = head    # the node after head now points back to it
    head.next = None         # cut the old forward link
    return new_head

Python has no built-in singly linked list, so the tasks define ListNode plus two helpers: build(values) turns a Python list into a linked list, and to_list(head) turns it back, which makes results easy to check.

Common mistakes

  • Changing cur.next before saving it, which loses the rest of the list. Fix: nxt = cur.next first.
  • Forgetting to return the new head after reversing or deleting the first node. Fix: always return the head, and use the returned value.
  • Reading node.next.val when node.next is None (AttributeError: 'NoneType' object has no attribute 'val'). Fix: check node and node.next first.
  • In recursive reversal, forgetting head.next = None, which leaves a two-node cycle at the end. Fix: cut the old link.
  • Special-casing the head in every function. Fix: use a dummy node.

Need help?

Stuck on a word or an idea? Search this course, or every course, and jump straight to the explanation or a worked question.

Try:

Watch the lesson

Linked lists · 1/8

Linked lists

This lesson is about linked lists: chains of nodes joined by pointers.

Prefer a video by a teacher? Find free videos on “linked list explained reverse a linked list iterative recursive python” on YouTube ↗

In real life: A music playlist with Next and Previous

A music app's play queue needs Next, Previous, and removing the current song, all instantly, even for a long queue. A doubly linked list fits: each song points to the songs before and after it, so moving or removing is a couple of pointer changes, O(1), with no shifting. Real apps add a hash map from song ID to node so they can find any song in O(1) too.

python
class Track:
    def __init__(self, title):
        self.title = title
        self.prev = self.next = None

def link(*tracks):
    for a, b in zip(tracks, tracks[1:]):
        a.next, b.prev = b, a
    return tracks[0]

now = link(Track("Morning raga"), Track("Road trip"), Track("Lo-fi study"))
now = now.next          # Next
print(now.title)
now = now.prev          # Previous
print(now.title)
Shows Road trip, then Morning raga.

Practice (0/3 done)

Task 1 · Code · Python

Write reverse_list(head) that reverses a singly linked list IN PLACE (relink the existing nodes, do not create new ones) and returns the new head. Do it iteratively with three pointers, O(n) time and O(1) extra space; recursion would fail, because a test uses a list of 5,000 nodes. ListNode, build() and to_list() are given; keep them.

Task 2 · Code · Python

Write remove_elements(head, val) that removes EVERY node whose value equals val and returns the new head (which may be None). Use a dummy node so removing the head needs no special case. O(n) time, O(1) extra space. Example: 1 -> 2 -> 6 -> 3 -> 6 with val 6 becomes 1 -> 2 -> 3.

Task 3 · Code · Python

Write insert_at(head, index, val) that inserts a new node with value val so that it ends up at position index (0-based) and returns the head. index 0 makes it the new head; index equal to the length appends it at the end. If index is negative or greater than the length, return the list unchanged. O(index) time. Example: 1 -> 2 -> 3 with index 1 and val 9 becomes 1 -> 9 -> 2 -> 3.

Quick quiz

Question 1

1.What does it cost to insert a new node at the head of a singly linked list with n nodes?

Question 2

2.Why can you not read the 5th node of a linked list in O(1)?

Question 3

3.To insert node new after node prev, which order is correct?

Question 4

4.What is the extra space of reversing a list recursively?

Question 5

5.What problem does a dummy node solve?

Read more (free)

Open a question to see the answer and why. The first ones are this topic's own quiz, so try the quiz above before you peek.

What does it cost to insert a new node at the head of a singly linked list with n nodes?

Answer: O(1)

Point the new node at the old head; nothing else moves.

Why can you not read the 5th node of a linked list in O(1)?

Answer: Nodes are not side by side in memory, so you must follow next pointers from the head

There is no address formula like start + i × size; you walk i steps.

To insert node new after node prev, which order is correct?

Answer: new.next = prev.next, then prev.next = new

Link the new node to the rest first; otherwise prev.next already points at new and the rest is lost.

What is the extra space of reversing a list recursively?

Answer: O(n), for the call stack

Each node adds one nested call, and long lists can hit Python's recursion limit.

What problem does a dummy node solve?

Answer: Deleting or inserting at the head no longer needs special code

With a node before the head, every real node has a predecessor; return dummy.next at the end.

Why does push_front take Node **head?Related

Answer: To change the caller's head pointer

The function must update the caller's pointer.

From Singly linked lists

With while fast and fast.next, which node does slow stop at for 10 → 20 → 30 → 40?Related

Answer: 30

For an even length this loop gives the second middle: slow moves to 20, then 30, while fast moves to 30, then off the end.

From Fast and slow pointers and merging lists

What does inserting at index 0 of a list of n items cost?Related

Answer: O(n)

Every existing item must shift one place to the right.

From Arrays: traversal and in-place updates

In Python, which call removes the least recently used key from an OrderedDict used as an LRU cache?Related

Answer: popitem(last=False)

Keys are kept from oldest to newest; last=False removes from the oldest end.

From Mock problems: design-style data structures

Pass the quiz (60% or more) or mark this topic as done when you are ready.

Your progress is saved on this device only.

Next steps

  1. 1 more topic in “Stacks, queues and linked lists” before its exam: Fast and slow pointers and merging lists.
  2. Module 4 exam: Stacks, queues and linked lists · 14 questions · 25 min, without help. 5 topics of this module still to finish first.
  3. Then the next module, Recursion and backtracking. 61 topics left in the course before the final exam.

← Previous: Monotonic stacks and queues