Data Structures

Linked Lists

● Beginner ⏱ 10 min read data-structures

A linked list is a sequence of nodes where each node stores a value and a pointer to the next node. Unlike arrays, nodes are not stored contiguously in memory — they are scattered and connected by references.

What Is a Linked List?

Text
Head
 |
[10] → [20] → [30] → [40] → None

Each box = Node(value, next_pointer)
Head points to the first node.
Last node's next = None (sentinel).

The Node Class

Python
class Node:
    def __init__(self, value):
        self.value = value
        self.next = None   # pointer to the next node

class LinkedList:
    def __init__(self):
        self.head = None   # empty list
        self.size = 0

    def __len__(self):
        return self.size

    def __repr__(self):
        nodes, cur = [], self.head
        while cur:
            nodes.append(str(cur.value))
            cur = cur.next
        return ' → '.join(nodes) + ' → None'

# Usage
ll = LinkedList()
print(ll)   # → None

Traversal

Start at head and follow next pointers until you reach None. Traversal is always O(n).

Python
def traverse(self):
    current = self.head
    while current is not None:
        print(current.value)
        current = current.next

def search(self, target):
    current = self.head
    index = 0
    while current:
        if current.value == target:
            return index
        current = current.next
        index += 1
    return -1   # not found

Insertion

Python
def prepend(self, value):
    """Insert at head — O(1)"""
    new_node = Node(value)
    new_node.next = self.head   # new node points to old head
    self.head = new_node        # head moves to new node
    self.size += 1

def append(self, value):
    """Insert at tail — O(n): must walk to the end"""
    new_node = Node(value)
    if self.head is None:
        self.head = new_node
    else:
        current = self.head
        while current.next:       # walk to last node
            current = current.next
        current.next = new_node   # link last node to new
    self.size += 1

def insert_after(self, target, value):
    """Insert after a value — O(n)"""
    current = self.head
    while current:
        if current.value == target:
            new_node = Node(value)
            new_node.next = current.next
            current.next = new_node
            self.size += 1
            return
        current = current.next

Deletion

Python
def delete(self, value):
    """Remove first occurrence of value — O(n)"""
    if self.head is None:
        return

    # Special case: deleting the head
    if self.head.value == value:
        self.head = self.head.next
        self.size -= 1
        return

    # Walk until prev.next holds the target node
    prev, current = None, self.head
    while current:
        if current.value == value:
            prev.next = current.next   # bypass the node
            self.size -= 1
            return
        prev, current = current, current.next
💡
Doubly linked lists

A doubly linked list stores both next and prev pointers. This allows O(1) deletion when you already have a reference to the node — without needing to walk from the head to find the predecessor.

Linked List vs Array

OperationArray (list)Linked List
Access by indexO(1)O(n)
Insert at headO(n)O(1)
Insert at tailO(1) amortizedO(n) singly / O(1) with tail pointer
Insert in middleO(n)O(n) to find + O(1) to link
Delete headO(n)O(1)
Delete by valueO(n)O(n)
SearchO(n)O(n)
MemoryContiguous blockScattered + pointer overhead