Skip to content

Linked List Visualizer

Singlyn = 6≈ 72 B
from head:12→7→33→5→21→48→NULL
1/1

What is a linked list?

A linked list is a linear data structure made of nodes, where each node stores a value and a pointer to the next node, so the elements can sit anywhere in memory. Inserting or deleting next to a node you already hold is O(1) pointer surgery, but reaching the i-th element is O(n), because the only way in is to follow the pointers from the head.

How it works

  1. Each node holds data and a next pointer. The list itself only stores head, the address of the first node (plus tail if it keeps one).
  2. The last node's next is NULL, which marks the end. An empty list is simply head = NULL.
  3. Traverse: start with curr = head and repeat curr = curr.next until curr is NULL. Search, length and printing all work this way.
  4. Insert after a node: allocate the new node, set new.next = curr.next first, then curr.next = new. Inserting at the head is the same move with head in place of curr.next.
  5. Delete after a node: set prev.next = prev.next.next and free the unlinked node. Deleting the head just moves head one node forward.

Linked list vs array

An array keeps its elements in one contiguous block, so element i lives at base + i × size: O(1) access. A linked list gives that up — its nodes are allocated one at a time wherever memory is free, tied together only by next pointers — and gets cheap structural change in return. Once you stand on the right node, inserting or deleting rewires two pointers and moves nothing else, and the list never needs resizing.

  • Access by index: array O(1), linked list O(n).
  • Insert or delete at the front: array O(n) (everything shifts), linked list O(1).
  • Insert or delete in the middle: both need O(n) to reach the spot; after that the list is O(1) and the array O(n).
  • Memory and speed: every node also stores one or two pointers (8 bytes each on a 64-bit machine), and scanning scattered nodes misses the CPU cache — so lists are often slower in practice even when the big-O matches.

Singly, doubly and circular linked lists

The three classic variants differ only in which pointers each node carries, but that decides which operations are cheap.

  • Singly linked list — [data | next]. The lightest, and forward-only. Deleting a node needs its predecessor, so deleting the tail is O(n) even with a tail pointer.
  • Doubly linked list — [prev | data | next]. One more pointer per node buys backward traversal and O(1) deletion of any node you hold; with the usual head and tail pointers both ends are O(1). Inserts write four pointers instead of two.
  • Circular linked list — the last node points back to the head. A pointer to the last node reaches both ends in O(1), which suits round-robin scheduling; loops stop when they return to the head.

Use singly for a stack, or a queue with a tail pointer; doubly when you remove arbitrary nodes or walk both ways (an LRU cache); circular when the data really is a ring. The List type switch above converts the list on screen so you can compare them.

Worked example: insert 99 at index 2

Take the list the visualizer opens with: 12 → 7 → 33 → 5 → 21 → 48 → NULL. To put 99 at index 2 you need the node before that position — index 1, value 7 — so walk one step from the head: curr = head (12), then curr = curr.next (7).

Now two pointer writes, in this order:

  • new.next = curr.next — 99 now points at 33. Nothing in the list has changed yet; 99 is just aiming at the right place.
  • curr.next = new — 7 now points at 99, and the list reads 12 → 7 → 99 → 33 → 5 → 21 → 48.

The order is the whole trick. Do the second write first and 7.next is overwritten before anyone saved the address of 33: the nodes 33, 5, 21 and 48 become unreachable — a memory leak in C, silently lost data everywhere else. The Wrong order button in the visualizer plays exactly that mistake so you can watch the rest of the list fall away.

Common linked list mistakes

  • Losing the rest of the list by overwriting a next pointer before saving it.
  • Dereferencing NULL — reading curr.next.data on the last node, or running any operation on an empty list.
  • Forgetting the head or tail pointer when the node you insert or delete sits at either end. Test with 0, 1 and 2 nodes.
  • Off-by-one walks — to insert at index i, stop on node i − 1, not on node i.

A dummy (sentinel) node in front of the real head gives every real node a predecessor, so insert and delete need no if index == 0 branch.

Where linked lists are used

Linked lists rarely beat a dynamic array for plain storage, but they shine when elements must be spliced in and out without moving anything else:

  • LRU caches pair a hash map with a doubly linked list, so "move to front" and "evict the oldest" are both O(1).
  • Stacks and queues — a singly linked list with head (and tail) pointers gives O(1) push, pop and enqueue with no resizing.
  • Graph adjacency lists, hash-table buckets with separate chaining, and the free lists inside memory allocators.
  • Operating-system kernels — Linux links processes, timers and devices with an intrusive circular doubly linked list (list_head).
  • Standard libraries — Java's LinkedList and C++'s std::list are doubly linked; Python's collections.deque is a doubly linked list of fixed-size blocks.

Linked lists in coding interviews

Most interview problems combine four moves, each animated on this site: reverse in place with three pointers; slow and fast pointers to find the middle or detect a cycle (Floyd's tortoise and hare); a dummy head to merge sorted lists or delete without special cases; and gap pointers to remove the k-th node from the end.

Interviewers mostly check the empty list, the one-node list, and the head and tail. Narrate each pointer write in order, exactly as the status line above does.

Time and space complexity

CaseTimeSpaceWhy
Access by indexO(n)O(1)Follow next i times from the head; there is no address arithmetic.
Search by valueO(n)O(1)Linear scan; the best case, a match at the head, is O(1).
Insert at headO(1)O(1)Two pointer writes: new.next = head, then head = new.
Insert at tailO(1) / O(n)O(1)O(1) with a tail pointer; without one you walk to the last node.
Insert or delete after a known nodeO(1)O(1)Only the neighbouring pointers change; nothing is shifted.
Delete tail (singly)O(n)O(1)The new tail is the second-to-last node, which only a walk finds. Doubly linked: O(1).
Storage—O(n)One node per element, each with one (singly) or two (doubly) pointers.

Linked List in Python

class Node:
    def __init__(self, data):
        self.data = data
        self.next = None  # next node


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

    def insert_at_head(self, value):
        new_node = Node(value)
        new_node.next = self.head
        self.head = new_node

    def insert_at_tail(self, value):
        new_node = Node(value)
        if self.head is None:
            self.head = new_node
            return
        curr = self.head
        while curr.next is not None:
            curr = curr.next
        curr.next = new_node

    def search(self, value):
        curr, i = self.head, 0
        while curr is not None:
            if curr.data == value:
                return i
            curr, i = curr.next, i + 1
        return -1

    def delete_head(self):
        if self.head is None:
            raise IndexError("delete from empty list")
        temp = self.head
        self.head = self.head.next
        del temp  # free(temp): the GC reclaims it

    def delete_value(self, value):
        if self.head is None:
            return False
        if self.head.data == value:
            self.delete_head()
            return True
        prev = self.head
        while prev.next is not None:
            if prev.next.data == value:
                break
            prev = prev.next
        if prev.next is None:
            return False  # not found
        temp = prev.next
        prev.next = temp.next
        del temp  # free(temp): the GC reclaims it
        return True

The visualizer above shows the same code in Python, C++, Java and JavaScript.

Frequently asked questions

Is a linked list faster than an array?

Only for some operations. Inserting or deleting at the front, or next to a node you already hold, is O(1) in a list and O(n) in an array. But indexing is O(n) instead of O(1), and scanning a list is usually slower in practice because its nodes are scattered in memory and miss the CPU cache.

Why is accessing an element of a linked list O(n)?

Nodes are not stored next to each other, so there is no formula for the address of element i. The only way to reach it is to start at the head and follow next i times.

What is the difference between a singly and a doubly linked list?

A singly linked node has one pointer, next, and can only be walked forward. A doubly linked node also has prev, so you can walk backwards and delete any node or the tail in O(1), at the cost of one extra pointer per node.

Does Python have a built-in linked list?

Not as a general list type: Python's list is a dynamic array. collections.deque is implemented as a doubly linked list of fixed-size blocks and gives O(1) appends and pops at both ends; for a textbook linked list you write a small Node class like the one above.

When should I use a linked list instead of an array?

When you insert and delete far more often than you index — especially at the ends or at nodes you already hold — and when elements must keep a stable address while others come and go, as in LRU caches, free lists and kernel data structures.

What is a dummy (sentinel) node in a linked list?

A placeholder node in front of the real head that holds no data. Because every real node then has a predecessor, insert and delete need no special case for index 0, which removes a whole class of edge-case bugs.

Keep learning

Last updated