Phase 2 · Linear StructuresModule 5~38 min read

Singly Linked Lists

Nodes joined by pointers — insert and delete in O(1), with no shifting, at the cost of no random access.

What you'll learn

A linked list stores each element in its own node, and each node holds a pointer to the next one. The nodes can live anywhere in memory — following the chain is how you get around. That flexibility flips the array's trade-offs on their head.

By the end you'll be able to:

  • Describe a node and how pointers chain nodes together
  • Insert and delete in O(1) by rewiring pointers
  • Explain why there's no random access — reaching index i is O(n)
  • Choose between an array and a linked list

Nodes & pointers

Each node bundles a value with a next pointer to the following node. A single head reference marks the start, and the last node's next is null — the end of the chain. There's no index arithmetic here: to reach the 5th node you must hop through the first four.

Insert at the head

The linked list's signature move: adding to the front is O(1), no matter how long the list is. Just point the new node at the current head, then move the head. Watch a list built entirely by head-insertion:

Building a list by inserting at the head
prepend 10, 20, 30
head → null
1/4Start with an empty list: head → null.
No elements move — only two pointers change per insertion.

Deletion

Deleting a node is just re-linking: point the previous node past the one being removed. The work is finding the node (O(n)); the unlink itself is O(1):

Deleting a node by re-linking
delete 20
HEAD
30
→
20
→
TAIL
10
→ null
1/3delete(20): walk from the head. Is 30 the target? No — remember 30 as the predecessor and move on.

Note

Because nodes point one way only, deleting a node usually means tracking the previous node as you walk — you need it to rewire the chain. Module 6's doubly linked list removes that hassle.

In code

Language
linkedlist.py
class Node:
    def __init__(self, value):
        self.value = value
        self.next = None

class LinkedList:
    def __init__(self):
        self.head = None

    def prepend(self, value):        # insert at head: O(1)
        node = Node(value)
        node.next = self.head
        self.head = node

    def search(self, value):         # O(n)
        node = self.head
        while node:
            if node.value == value:
                return True
            node = node.next
        return False

Arrays vs linked lists

OperationArrayLinked list
access index iO(1)O(n)
searchO(n)O(n)
insert / delete at headO(n)O(1)
insert / delete at tailO(1) amortizedO(1)* with a tail pointer
MemoryCompact, contiguousExtra pointer per node

Key idea

Use an array when you index and iterate a lot; use a linked list when you insert and delete at the ends frequently and rarely need random access.

Recap & quick check

Key takeaways

  • A linked list is nodes joined by next pointers, starting from a head reference.
  • Inserting or deleting at the head is O(1) — just rewire pointers, nothing shifts.
  • There's no random access: reaching index i is O(n) because you follow the chain.
  • Deletion is O(1) once the node is found; finding it is O(n).
  • Arrays win at indexing; linked lists win at end insertion/deletion.

Quick check

1. What does each node in a singly linked list contain?

2. Inserting at the head of a linked list is:

3. Why is accessing the i-th element O(n)?

4. Compared to arrays, linked lists use more memory because:

One-way chains are handy but limited. Next we add a backward pointer and close the loop. Next up: Module 6 — Doubly & Circular Linked Lists.