Skip to content

Linked List Algorithms: Code Templates in 6 Languages

If you have not read the concept guide yet, start there for the intuition and complexity analysis. This page gives you the pointer-manipulation code you can memorize and adapt during an interview. Every template here runs in

O(N)
time with
O(1)
extra space unless otherwise noted.

Main Template: Reverse a Linked List

This is the most common linked list operation and the building block for palindrome checks, reordering, and group reversal. Three pointers walk the list: prev tracks the reversed prefix, current is the node being processed, and next keeps the rest of the list reachable.

Use this for Reverse Linked List .

function reverseList(head) {
    let prev = null;
    let current = head;

    while (current !== null) {
        // Save the node after current before its pointer is
        // overwritten, or the rest of the list is unreachable.
        const next = current.next;
        current.next = prev;
        prev = current;
        current = next;
    }

    return prev;
}
Templates only help if you can recall them without staring at the screen. Turn these snippets into flashcards for daily mobile review.

Code Breakdown

Key Variables

  • prev: the last processed node. It starts as null because the first node points to nothing once the list is reversed.
  • current: the node being processed. It starts at the head and walks forward.
  • next: the saved successor of current, captured before current.next is overwritten.

Visual Mechanism

    stateDiagram-v2
    [*] --> Init: prev = null, current = head
    Init --> Check: current != null
    Check --> Save: next = current.next
    Save --> Rewire: current.next = prev
    Rewire --> Advance: prev = current, current = next
    Advance --> Check
    Check --> Done: current == null
    Done --> [*]: return prev
  

Critical Sections

The initialization sets prev to null because the first node becomes the last node of the reversed list. Its pointer must point to nothing.

The save step is the part people skip under pressure. current.next is about to be overwritten, and next is the only reference to the rest of the list. Without it, the loop terminates after one iteration.

The advance step moves the whole window forward. prev takes the position of current, and current takes the saved next. Both assignments must happen before the next iteration, and the order between them does not matter.

Variations

1. Find the Middle Node (Fast and Slow)

When you need the middle of a list, or a split point for palindrome and reorder problems, one pointer moving twice as fast as the other lands at the middle by the time it reaches the end.

Use this for Middle of the Linked List and Palindrome Linked List .

function findMiddle(head) {
    let slow = head;
    let fast = head;

    // fast covers two nodes per step, so it reaches the end
    // exactly when slow has covered half the list.
    while (fast !== null && fast.next !== null) {
        slow = slow.next;
        fast = fast.next.next;
    }

    return slow;
}

Visual

    graph LR
    A["slow = fast = head"] --> B{"fast and fast.next exist?"}
    B -->|Yes| C["slow +1, fast +2"]
    C --> B
    B -->|No| D["return slow"]
  

2. Remove the k-th Node From the End (Dummy Node)

When a problem asks for the k-th node from the end, two pointers separated by k steps find it in one pass without knowing the list length. The dummy head protects the case where the node to remove is the head itself.

Use this for Remove Nth Node From End of List .

function removeNthFromEnd(head, n) {
    // The dummy node makes head removal identical to any other
    // removal, so the delete code has no special case.
    const dummy = new ListNode(0, head);
    let first = dummy;
    let second = dummy;

    // first starts n+1 nodes ahead, so when it reaches the end,
    // second is the predecessor of the target node.
    for (let i = 0; i <= n; i++) {
        first = first.next;
    }

    while (first !== null) {
        first = first.next;
        second = second.next;
    }

    second.next = second.next.next;
    return dummy.next;
}

Visual

    graph LR
    A["dummy -> head"] --> B["first advances n+1 steps"]
    B --> C["first and second walk together"]
    C --> D["first reaches null"]
    D --> E["second sits before the target node"]
    E --> F["skip the target node"]
  

3. Merge Two Sorted Lists (Dummy Node)

Merging two sorted lists builds a new list with a tail pointer. The dummy node means the first append needs no special case, and the same loop handles both lists.

The practice problem Merge Two Sorted Lists is covered on the divide and conquer problems page .

function mergeTwoLists(list1, list2) {
    // dummy.next is the result head, so appending the first
    // node needs no special case.
    const dummy = new ListNode(0);
    let tail = dummy;

    while (list1 !== null && list2 !== null) {
        if (list1.val < list2.val) {
            tail.next = list1;
            list1 = list1.next;
        } else {
            tail.next = list2;
            list2 = list2.next;
        }
        tail = tail.next;
    }

    // One list is exhausted. The other is already sorted,
    // so append what is left of it whole.
    tail.next = list1 !== null ? list1 : list2;
    return dummy.next;
}

Visual

    graph LR
    A["dummy and tail"] --> B{"both lists non-empty?"}
    B -->|Yes| C["append the smaller head, advance that list"]
    C --> B
    B -->|No| D["append the rest of the remaining list"]
    D --> E["return dummy.next"]
  

4. Detect a Cycle (Floyd’s Algorithm)

A cycle makes a naive traversal run forever. Two pointers at different speeds meet inside a cycle, and they can only meet if a cycle exists.

The practice problems Linked List Cycle and Linked List Cycle II are covered on the cycle detection problems page .

function hasCycle(head) {
    let slow = head;
    let fast = head;

    // The second step of fast dereferences fast.next, so the
    // loop must confirm it exists on every iteration.
    while (fast !== null && fast.next !== null) {
        slow = slow.next;
        fast = fast.next.next;

        // In a cycle, fast gains one node per step on slow
        // and eventually laps it. Meeting proves a cycle.
        if (slow === fast) return true;
    }

    return false;
}

Visual

    graph LR
    A["slow = fast = head"] --> B{"fast and fast.next exist?"}
    B -->|Yes| C["slow +1, fast +2"]
    C --> D{"slow == fast?"}
    D -->|Yes| E["cycle found"]
    D -->|No| B
    B -->|No| F["no cycle"]
  
Each variation above is a separate review card in the app. Drill them individually so you do not mix up the conditions under pressure.

Now head to the practice problems to apply these templates to real interview questions.

Now that you have the templates, the next step is application. Head to the practice problems or start reviewing these templates with spaced repetition .