Back

Singly Linked Lists

Today

A singly linked list is a collection of nodes where each node holds a value and a pointer (reference) to the next node. Unlike arrays that store data contiguously in memory, linked lists scatter their nodes around RAM, connected via pointers. This flexibility comes with trade-offs worth understanding.

Core Structure

Every node is an object with two things:

Imagine a chain: each link knows about the next link, but the links don't have to sit next to each other in memory. The head pointer tracks where the list starts, the tail pointer (if maintained) tracks the end.

Memory Layout

Here's the key difference from arrays: the physical memory order doesn't match the logical order. Node 1 might be at address $100, Node 2 at $500, Node 3 at $250. But their next pointers connect them logically, so we traverse them in the right sequence.

Traversal

To walk through a linked list:

current = head
while current:
    print(current.value)
    current = current.next

We follow the chain until current becomes null (no more nodes). Time complexity is O(n) where n is the list size, just like looping through an array.

Operations

Insert at End

If we maintain a tail pointer, inserting at the end is O(1):

  1. Create a new node
  2. Set tail.next to the new node
  3. Update tail to point to the new node

Without a tail pointer, it's O(n) because we have to traverse to find the end.

Remove a Node

If we have a pointer to the previous node, removal is O(1):

Set previous.next = current.next, effectively skipping over the node we want to remove. The old node becomes unreachable (garbage collected).

In the general case where we need to search for the node first, it's O(n).

Insert/Remove at Start

With a head pointer, both are O(1):

Why Linked Lists?

Arrays give constant-time random access (index lookup) but require shifting for middle inserts/removes. Linked lists need O(n) to find an element but only O(1) to remove once found. Pick based on your workload.

Advantages:

Disadvantages:

Complexity Summary

OperationTimeNotes
Access by indexO(n)Must traverse from head
Insert at startO(1)If you have head pointer
Insert at endO(1)If you maintain tail pointer
Insert/remove in middleO(n)Need to find the location first
Remove at startO(1)If you have head pointer

Implementation

Full code with tests in the git repo.

class ListNode:
    def __init__(self, value):
        self.value = value
        self.next = None
 
class SinglyLinkedList:
    def __init__(self):
        self.head = None
        self.tail = None
    
    def insert_end(self, value):
        new_node = ListNode(value)
        if not self.head:
            self.head = self.tail = new_node
        else:
            self.tail.next = new_node
            self.tail = new_node
    
    def insert_start(self, value):
        new_node = ListNode(value)
        if not self.head:
            self.head = self.tail = new_node
        else:
            new_node.next = self.head
            self.head = new_node
    
    def remove_start(self):
        if self.head:
            self.head = self.head.next
            if not self.head:
                self.tail = None
        
    def remove(self, value):
        if not self.head:
            return 
        # If head matches
        if self.head.value == value:
            self.remove_start()
            return
        # Search for the node
        current = self.head
        while current.next:
            if current.next.value == value:
                current.next = current.next.next
                if not current.next:
                    self.tail = current
                return
            current = current.next
 
    def print_list(self):
        current = self.head
        while current:
            print(current.value, end=" -> ")
            current = current.next
        print("None")