Linked Lists· 20 min

Singly Linked List

Insert, delete, search with pointer rewiring

Setup

insert value:
Step 1 / 1

Current step

1 / 1

Enter a value and click Run to visualize the operation.

Step log

head
▼
[0]
data10
next●
▶
[1]
data25
next●
▶
[2]
data40
next∅
tail
null
▼
Length: 3Type: singlyHead value: 10
singly-list.java
1class Node { int value; Node next; Node(int v){value=v;} }
2class LinkedList {
3 Node head;
4 void insertAtEnd(int v) {
5 Node node = new Node(v);
6 if (head == null) { head = node; return; }
7 Node cur = head;
8 while (cur.next != null) cur = cur.next;
9 cur.next = node;
10 }
11 void delete(int v) {
12 if (head == null) return;
13 if (head.value == v) { head = head.next; return; }
14 Node cur = head;
15 while (cur.next != null && cur.next.value != v) cur = cur.next;
16 if (cur.next != null) cur.next = cur.next.next;
17 }
18}
● Java

Singly Linked List

Each node holds a value and a next pointer. Traversal is one-directional. Memory overhead is one pointer per node. Insertion/deletion at head is O(1); at tail is O(n) without a tail pointer.

Access
O(n)
Search
O(n)
Insert (head)
O(1)
Insert (tail)
O(n)
Delete (known)
O(n)
Space
O(n)

Written guide

Singly Linked List: complete guide

A singly linked list stores values in nodes that each point to the next node, so inserting or deleting is just a matter of rewiring one or two pointers instead of shifting array elements. This lesson walks through insert, delete, and search, and the pointer-ordering habits that keep you from losing half your list.

Intuition

Think of a treasure hunt. Each clue card tells you something and also tells you where the next card is hidden. You cannot jump straight to clue number seven; you have to follow cards one through six. But if the organiser wants to add a new clue between cards three and four, nobody has to move any cards around. They just rewrite card three to point at the new card, and make the new card point at card four.

A singly linked list is that chain of cards. Each node holds a value and a next reference. The list itself only remembers the head, the first node. The last node's next is null. Compared with an array you give up instant indexing (no list[7] in O(1)), and in return you gain cheap insertion and deletion anywhere you already have a pointer, plus the ability to grow without ever resizing or copying.

How it works step by step

Every operation is a small variation on walking the chain with a cur (or prev) pointer and then changing at most two next fields.

  1. Insert at head: create a node, set node.next = head, then head = node. O(1).
  2. Insert after a node `prev`: set node.next = prev.next first, then prev.next = node. Reversing the order makes prev.next point at the new node before you saved the rest of the list, and the rest is lost.
  3. Insert at index i: walk i - 1 steps from the head to find prev, then do the insert-after step.
  4. Delete by value: walk with prev until prev.next holds the value, then bypass it with prev.next = prev.next.next. A dummy node in front of the head means deleting the head needs no special case.
  5. Search: walk from the head, comparing each value, until you find it or reach null.

Dry run

Start with the list 3 -> 7 -> 9 and apply five operations. The arrows show next pointers.

StepOperationPointer changesList after
0Starthead = node(3)3 -> 7 -> 9 -> null
1insertAt(1, 5)prev = node(3); new.next = node(7); node(3).next = new3 -> 5 -> 7 -> 9 -> null
2remove(7)prev walks dummy, 3, 5; node(5).next = node(9)3 -> 5 -> 9 -> null
3addFirst(1)new.next = node(3); head = new1 -> 3 -> 5 -> 9 -> null
4indexOf(9)visit 1, 3, 5, 9: found at index 3unchanged
5remove(4)prev reaches the last node, prev.next is null: return falseunchanged

In step 2, node 7 is not destroyed explicitly. Nothing points to it any more, so in Java or Python the garbage collector reclaims it. In C or C++ you would have to free it yourself, after saving any pointer you still need.

Code

SinglyLinkedList.java
public class SinglyLinkedList {
    private static class Node {
        int val;
        Node next;
        Node(int val) { this.val = val; }
    }

    private Node head;
    private int size;

    public void addFirst(int val) {
        Node n = new Node(val);
        n.next = head;
        head = n;
        size++;
    }

    public void addLast(int val) {
        Node n = new Node(val);
        if (head == null) {
            head = n;
        } else {
            Node cur = head;
            while (cur.next != null) cur = cur.next;
            cur.next = n;
        }
        size++;
    }

    /** Inserts val so that it ends up at position index (0-based). */
    public void insertAt(int index, int val) {
        if (index < 0 || index > size) throw new IndexOutOfBoundsException("index " + index);
        if (index == 0) { addFirst(val); return; }
        Node prev = head;
        for (int i = 0; i < index - 1; i++) prev = prev.next;
        Node n = new Node(val);
        n.next = prev.next; // 1. new node grabs the rest of the list
        prev.next = n;      // 2. predecessor points at the new node
        size++;
    }

    /** Removes the first node holding val. Returns true if one was removed. */
    public boolean remove(int val) {
        Node dummy = new Node(0);
        dummy.next = head;
        Node prev = dummy;
        while (prev.next != null && prev.next.val != val) prev = prev.next;
        if (prev.next == null) return false;
        prev.next = prev.next.next; // bypass the node
        head = dummy.next;          // head changes if we removed the first node
        size--;
        return true;
    }

    public int indexOf(int val) {
        int i = 0;
        for (Node cur = head; cur != null; cur = cur.next, i++) {
            if (cur.val == val) return i;
        }
        return -1;
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        for (Node cur = head; cur != null; cur = cur.next) sb.append(cur.val).append(" -> ");
        return sb.append("null").toString();
    }

    public static void main(String[] args) {
        SinglyLinkedList list = new SinglyLinkedList();
        list.addLast(3); list.addLast(7); list.addLast(9);
        list.insertAt(1, 5);
        list.remove(7);
        list.addFirst(1);
        System.out.println(list + "  indexOf(9) = " + list.indexOf(9));
    }
}
singly_linked_list.py
class Node:
    def __init__(self, val, next=None):
        self.val = val
        self.next = next


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

    def add_first(self, val):
        self.head = Node(val, self.head)
        self.size += 1

    def add_last(self, val):
        node = Node(val)
        if self.head is None:
            self.head = node
        else:
            cur = self.head
            while cur.next:
                cur = cur.next
            cur.next = node
        self.size += 1

    def insert_at(self, index, val):
        if not 0 <= index <= self.size:
            raise IndexError(index)
        if index == 0:
            self.add_first(val)
            return
        prev = self.head
        for _ in range(index - 1):
            prev = prev.next
        prev.next = Node(val, prev.next)  # new node takes prev.next first
        self.size += 1

    def remove(self, val):
        dummy = Node(0, self.head)
        prev = dummy
        while prev.next and prev.next.val != val:
            prev = prev.next
        if prev.next is None:
            return False
        prev.next = prev.next.next
        self.head = dummy.next
        self.size -= 1
        return True

    def index_of(self, val):
        for i, v in enumerate(self):
            if v == val:
                return i
        return -1

    def __iter__(self):
        cur = self.head
        while cur:
            yield cur.val
            cur = cur.next


lst = SinglyLinkedList()
for x in (3, 7, 9):
    lst.add_last(x)
lst.insert_at(1, 5)
lst.remove(7)
lst.add_first(1)
print(list(lst), lst.index_of(9))  # [1, 3, 5, 9] 3

Time and space complexity

OperationBestAverageWorstExtra space
Insert at headO(1)O(1)O(1)O(1)
Insert at tail (no tail pointer)O(n)O(n)O(n)O(1)
Insert at index iO(1) at i = 0O(i)O(n)O(1)
Delete by valueO(1) at headO(n)O(n)O(1)
SearchO(1)O(n)O(n)O(1)
Storage for n items---O(n), one pointer per node

The rewiring itself is always constant time; what costs O(n) is finding the spot, because the only way to reach node i is to follow i pointers. Keeping a tail reference makes appending O(1), which is how linked-list queues work. Deleting the tail stays O(n), though, since you still need the node before it.

Edge cases

  • Empty list: head is null; search returns -1, delete returns false, insert at 0 becomes the head.
  • Deleting the head: handled by the dummy node, or by an explicit head = head.next branch.
  • Deleting the only node: the list must end with head = null (and tail = null if you keep one).
  • Insert at index size: equivalent to appending at the tail.
  • Index out of range: validate before walking, or you will dereference null mid-walk.
  • Duplicate values: decide whether remove deletes the first match or all of them.

Common mistakes

  • Losing the next pointer. Writing prev.next = node before node.next = prev.next leaves the new node pointing at itself and orphans everything after it. Always save the old link first.
  • Dereferencing null. Loops like while (cur.next.val != val) crash at the end of the list. Check cur.next != null first.
  • Moving the head while traversing. Using head = head.next to walk the list destroys it. Walk with a separate cur.
  • Forgetting to update `head` after deleting or inserting at position 0.
  • Not updating `tail` and `size` in implementations that track them; they silently drift out of sync.
  • Walking one step too far. To insert at index i you need the node at i - 1, not at i.

Variations and when to use it

A tail pointer makes the list a perfect O(1) queue. A circular list links the last node back to the head, useful for round-robin scheduling. A doubly linked list adds a prev pointer so you can delete any node in O(1) and walk backwards, at the cost of extra memory per node. Many classic interview tricks are built on singly linked lists: reversing with three pointers, finding the middle with slow and fast pointers, cycle detection with Floyd's algorithm, and merging two sorted lists.

Use a singly linked list when you mostly insert or remove at the front, when you need a stack or queue that never resizes, or when an interview problem simply hands you a ListNode. Prefer an array or ArrayList when you need random access or care about cache performance; contiguous memory is much faster to scan than nodes scattered across the heap.

  • #206 Reverse Linked List — rewire each next backwards while saving the old next first.
  • #328 Odd Even Linked List — maintain two chains and splice the even chain after the odd one.
  • #2095 Delete the Middle Node of a Linked List — slow and fast pointers find the node before the middle, then bypass.
  • #2130 Maximum Twin Sum of a Linked List — find the middle, reverse the second half, then walk both halves together.
  • #203 Remove Linked List Elements — the dummy-node delete loop, removing every match.
  • #19 Remove Nth Node From End of List — a gap of n between two pointers locates the predecessor in one pass.
  • #21 Merge Two Sorted Lists — splice nodes from two lists onto a dummy tail.
  • #141 Linked List Cycle — fast pointer moves two steps, slow moves one; they meet only if there is a cycle.

FAQ

›When is a linked list better than an array?

When you insert and delete frequently at positions you already hold a pointer to, especially at the front, and rarely need random access. Arrays win for indexing, binary search, and fast sequential scans thanks to cache locality.

›What is a dummy (sentinel) node and why use it?

It is a placeholder node placed before the real head. Because every real node then has a predecessor, deleting or inserting at the head uses the same code as anywhere else, which removes a whole class of edge-case bugs.

›Why is accessing the k-th element O(n)?

Nodes are scattered in memory and only connected through next pointers, so the only way to reach the k-th node is to follow k pointers from the head.

›How do you delete a node when you only have a pointer to it?

In a singly linked list you cannot reach its predecessor, so the trick is to copy the next node's value into it and then delete the next node instead. This fails for the tail node, which is why doubly linked lists exist.

›Does Java's LinkedList use a singly or doubly linked list?

java.util.LinkedList is doubly linked, which lets it act as both a list and a deque with O(1) operations at either end.