A linked list is a data structure where elements — called nodes — are scattered anywhere in memory and connected using pointers, rather than sitting in one contiguous block like an array. Each node stores a value and a reference ("pointer") to the next node in the sequence. This single design choice flips the array's tradeoffs almost completely: you lose instant indexed access, but you gain cheap insertion and deletion.

Singly, Doubly, and Circular Linked Lists

A singly linked list is the simplest form: each node points only to the next node, so you can traverse forward but not backward. A doubly linked list adds a second pointer to the previous node in each node, allowing traversal in both directions at the cost of extra memory per node. A circular linked list is either variant with the last node's "next" pointer looping back to the first node instead of pointing to nothing — useful for things like round-robin scheduling, where you want to cycle through items endlessly.

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

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

    def append(self, value):
        new_node = Node(value)
        if not self.head:
            self.head = new_node
            return
        current = self.head
        while current.next:       # walk to the last node -- O(n)
            current = current.next
        current.next = new_node

    def to_list(self):
        values, current = [], self.head
        while current:
            values.append(current.value)
            current = current.next
        return values

ll = SinglyLinkedList()
for v in [10, 20, 30]:
    ll.append(v)
print(ll.to_list())

Output:

[10, 20, 30]

For a complete working implementation with insert-at-front and delete operations, see the Python singly linked list program.

Linked Lists vs Arrays: The Complexity Tradeoff

This is the comparison that matters most. Index access on a linked list is O(n) — there's no way to "jump" to the 5th node, you must walk the pointers one at a time starting from the head. But if you already hold a reference to a specific node, inserting or deleting right there is O(1): you just rewrite a couple of pointers, with no shifting of other elements required (unlike an array, where inserting in the middle means shifting every subsequent element).

# Inserting after a known node -- O(1), just pointer rewiring
def insert_after(node, value):
    new_node = Node(value)
    new_node.next = node.next
    node.next = new_node

Output:

No console output — the point is that this function does a fixed, constant amount of work no matter how long the list is, unlike an array's O(n) shift.

When to Reach for a Linked List

Linked lists shine when you're frequently inserting or removing elements at the front or in the middle, and you don't need random access by index — think of implementing a queue or managing a sequence where items are constantly added and removed. For everything else — especially anything requiring frequent lookups by position — a dynamic array usually wins, both for raw speed (better memory locality, no pointer-chasing) and simplicity.