Linked Lists

Nodes chained by pointers: O(1) to insert or delete where you stand, O(n) to find anything. Master reversal, dummy heads and fast and slow pointers.

BeginnerRead first: Big-O & Complexity

How It Works

A linked list stores each value in its own node, and each node holds a pointer to the next one. The list itself is just a pointer to the first node, the head, and the last node points to null. The nodes aren't stored side by side, so there's no indexing: to reach the fifth node you start at the head and follow four next pointers.

head
  |
  v
+---+---+    +---+---+    +---+---+
| 1 | o-+--->| 2 | o-+--->| 3 | o-+---> null
+---+---+    +---+---+    +---+---+
 val next
Each node holds a value and a pointer to the next node.

That's the trade against an array. Reaching position k costs O(k) instead of O(1). In exchange, once you're holding a node, inserting or removing next to it is O(1), because you rewrite a pointer or two and nothing else moves. An array would shift every later element. To insert x after node, set x.next = node.next and then node.next = x, in that order. To remove the node after node, set node.next = node.next.next.

Most linked list problems are built from three moves:

  • Reverse in place. Walk the list with prev and curr, pointing each next backwards as you go. That's the template below, and it shows up inside harder problems such as reordering a list or checking whether it's a palindrome.
  • Dummy head. Put a placeholder node in front of the result, so adding or removing the first real node isn't a special case, and return dummy.next at the end. Merge Two Sorted Lists uses one.
  • Fast and slow pointers. Move one pointer two nodes for every one the other moves. When the fast one reaches the end, the slow one is at the middle, and if the list loops, they meet inside the loop. It's the two pointers idea adapted to lists.

Reversing a List in Place

Step 1 of 11

  • curr, the node being flipped
  • nextNode, the rest of the list
  • Reversed part

prev starts at null, because nothing is reversed yet, and curr starts at the head, 1. Each pass flips one arrow.

All 11 steps as text
  1. prev starts at null, because nothing is reversed yet, and curr starts at the head, 1. Each pass flips one arrow.
  2. Save nextNode = curr.next, which is 2. The next line overwrites curr.next, and without this pointer you'd lose the rest of the list.
  3. Point curr.next back at prev. 1 now points at null, because the old head becomes the new tail.
  4. Step both forward: prev moves to 1 and curr to 2. Everything left of curr is reversed now.
  5. Save nextNode = curr.next, which is 3, before the flip overwrites it.
  6. Point curr.next back at prev. 2 now points at 1, so the reversed part reads 2 -> 1 -> null.
  7. Step both forward: prev moves to 2 and curr to 3. Everything left of curr is reversed now.
  8. Save nextNode = curr.next, which is null because 3 is the last node. The loop body is the same as before, with no special case.
  9. Point curr.next back at prev. 3 now points at 2, so the reversed part reads 3 -> 2 -> 1 -> null.
  10. Step both forward: prev moves to 3 and curr to null. curr has run off the end, so the loop stops.
  11. Return prev as the new head. The old tail 3 comes first now, and the list reads 3 -> 2 -> 1 -> null. You touched each node once, with O(1) extra space.

When to Reach for It

  • The input is a head node rather than an array.
  • You must rearrange nodes in place (reverse, reorder, merge or remove them) without copying the values into an array.
  • The question asks for the middle node, the kth node from the end, or whether the list loops, often in O(1) extra space.
  • You need O(1) inserts and deletes in the middle of a sequence, as in an LRU cache, which pairs a doubly linked list with a hash map.

Complexity

OperationTimeSpace
Reach the kth nodeO(k)O(1)
Search for a valueO(n)O(1)
Insert or delete next to a node you holdO(1)O(1)
Insert at the headO(1)O(1)
Reverse in placeO(n)O(1)

The Template

Build, Walk and Reverse a List

Each file defines a small ListNode with a value and a next pointer, then three functions. buildList creates nodes from the back of the array, so each new node can point at the list built so far. That's O(1) per node with no tail pointer. formatList is the standard walk: start at head and follow next until you reach null.

reverseList flips the list in place. curr is the node being flipped, and prev is the reversed part behind it. For each node, save curr.next first, because the next line overwrites it and you'd lose the rest of the list. Then point curr back at prev and step both forward.

start   null    1 -> 2 -> 3 -> null
        prev    curr

step 1  null <- 1    2 -> 3 -> null
                prev curr

step 2  null <- 1 <- 2    3 -> null
                     prev curr

step 3  null <- 1 <- 2 <- 3    null
                          prev curr
Each step flips one arrow. When curr runs off the end, prev is the new head.

Time O(n)Space O(1) to walk or reverse, O(n) to build

from __future__ import annotations


class ListNode:
    """One node of a singly linked list."""

    def __init__(self, val: int, next: ListNode | None = None):
        self.val = val
        self.next = next


def build_list(values: list[int]) -> ListNode | None:
    """Builds a list from `values`. Prepending from the back is O(1) per node."""
    head = None
    for value in reversed(values):
        head = ListNode(value, head)
    return head


def format_list(head: ListNode | None) -> str:
    """Walks the list from head to tail, e.g. "1 -> 2 -> 3 -> None"."""
    parts = []
    node = head
    while node is not None:
        parts.append(str(node.val))
        node = node.next
    parts.append("None")
    return " -> ".join(parts)


def reverse_list(head: ListNode | None) -> ListNode | None:
    """Reverses the list in place and returns the new head (the old tail)."""
    prev = None
    curr = head
    while curr is not None:
        next_node = curr.next  # Save the rest of the list before cutting the link.
        curr.next = prev  # Point this node back at the one before it.
        prev = curr
        curr = next_node
    return prev  # curr has walked off the end, so prev is the old tail.


if __name__ == "__main__":
    head = build_list([1, 2, 3, 4, 5])
    print(format_list(head))  # 1 -> 2 -> 3 -> 4 -> 5 -> None
    print(format_list(reverse_list(head)))  # 5 -> 4 -> 3 -> 2 -> 1 -> None
    print(format_list(reverse_list(build_list([7]))))  # 7 -> None
    print(format_list(reverse_list(build_list([]))))  # None
// One node of a singly linked list.
export class ListNode {
  val: number;
  next: ListNode | null;

  constructor(val: number, next: ListNode | null = null) {
    this.val = val;
    this.next = next;
  }
}

// Builds a list from values. Prepending from the back is O(1) per node.
export function buildList(values: number[]): ListNode | null {
  let head: ListNode | null = null;
  for (let i = values.length - 1; i >= 0; i--) head = new ListNode(values[i], head);
  return head;
}

// Walks the list from head to tail, e.g. "1 -> 2 -> 3 -> null".
export function formatList(head: ListNode | null): string {
  const parts: string[] = [];
  for (let node = head; node !== null; node = node.next) parts.push(String(node.val));
  parts.push("null");
  return parts.join(" -> ");
}

// Reverses the list in place and returns the new head (the old tail).
export function reverseList(head: ListNode | null): ListNode | null {
  let prev: ListNode | null = null;
  let curr = head;
  while (curr !== null) {
    const nextNode = curr.next; // Save the rest of the list before cutting the link.
    curr.next = prev; // Point this node back at the one before it.
    prev = curr;
    curr = nextNode;
  }
  return prev; // curr has walked off the end, so prev is the old tail.
}

const head = buildList([1, 2, 3, 4, 5]);
console.log(formatList(head)); // 1 -> 2 -> 3 -> 4 -> 5 -> null
console.log(formatList(reverseList(head))); // 5 -> 4 -> 3 -> 2 -> 1 -> null
console.log(formatList(reverseList(buildList([7])))); // 7 -> null
console.log(formatList(reverseList(buildList([])))); // null
package main

import (
	"fmt"
	"strconv"
	"strings"
)

// ListNode is one node of a singly linked list.
type ListNode struct {
	Val  int
	Next *ListNode
}

// buildList builds a list from values. Prepending from the back is O(1) per
// node.
func buildList(values []int) *ListNode {
	var head *ListNode
	for i := len(values) - 1; i >= 0; i-- {
		head = &ListNode{Val: values[i], Next: head}
	}
	return head
}

// formatList walks the list from head to tail, e.g. "1 -> 2 -> 3 -> nil".
func formatList(head *ListNode) string {
	parts := []string{}
	for node := head; node != nil; node = node.Next {
		parts = append(parts, strconv.Itoa(node.Val))
	}
	parts = append(parts, "nil")
	return strings.Join(parts, " -> ")
}

// reverseList reverses the list in place and returns the new head (the old
// tail).
func reverseList(head *ListNode) *ListNode {
	var prev *ListNode
	curr := head
	for curr != nil {
		nextNode := curr.Next // Save the rest of the list before cutting the link.
		curr.Next = prev      // Point this node back at the one before it.
		prev = curr
		curr = nextNode
	}
	return prev // curr has walked off the end, so prev is the old tail.
}

func main() {
	head := buildList([]int{1, 2, 3, 4, 5})
	fmt.Println(formatList(head))                             // 1 -> 2 -> 3 -> 4 -> 5 -> nil
	fmt.Println(formatList(reverseList(head)))                // 5 -> 4 -> 3 -> 2 -> 1 -> nil
	fmt.Println(formatList(reverseList(buildList([]int{7})))) // 7 -> nil
	fmt.Println(formatList(reverseList(buildList([]int{}))))  // nil
}
using System;
using System.Collections.Generic;

public class Solution
{
    // One node of a singly linked list.
    public class ListNode
    {
        public int Val;
        public ListNode? Next;

        public ListNode(int val, ListNode? next = null)
        {
            Val = val;
            Next = next;
        }
    }

    // Builds a list from values. Prepending from the back is O(1) per node.
    public static ListNode? BuildList(int[] values)
    {
        ListNode? head = null;
        for (int i = values.Length - 1; i >= 0; i--) head = new ListNode(values[i], head);
        return head;
    }

    // Walks the list from head to tail, e.g. "1 -> 2 -> 3 -> null".
    public static string FormatList(ListNode? head)
    {
        var parts = new List<string>();
        for (var node = head; node != null; node = node.Next) parts.Add(node.Val.ToString());
        parts.Add("null");
        return string.Join(" -> ", parts);
    }

    // Reverses the list in place and returns the new head (the old tail).
    public static ListNode? ReverseList(ListNode? head)
    {
        ListNode? prev = null;
        var curr = head;
        while (curr != null)
        {
            var nextNode = curr.Next; // Save the rest of the list before cutting the link.
            curr.Next = prev; // Point this node back at the one before it.
            prev = curr;
            curr = nextNode;
        }
        return prev; // curr has walked off the end, so prev is the old tail.
    }

    public static void Main()
    {
        var head = BuildList(new[] { 1, 2, 3, 4, 5 });
        Console.WriteLine(FormatList(head)); // 1 -> 2 -> 3 -> 4 -> 5 -> null
        Console.WriteLine(FormatList(ReverseList(head))); // 5 -> 4 -> 3 -> 2 -> 1 -> null
        Console.WriteLine(FormatList(ReverseList(BuildList(new[] { 7 })))); // 7 -> null
        Console.WriteLine(FormatList(ReverseList(BuildList(new int[0])))); // null
    }
}
import java.util.ArrayList;
import java.util.List;

public class Solution {
    // One node of a singly linked list.
    static class ListNode {
        int val;
        ListNode next;

        ListNode(int val, ListNode next) {
            this.val = val;
            this.next = next;
        }
    }

    // Builds a list from values. Prepending from the back is O(1) per node.
    static ListNode buildList(int[] values) {
        ListNode head = null;
        for (int i = values.length - 1; i >= 0; i--) head = new ListNode(values[i], head);
        return head;
    }

    // Walks the list from head to tail, e.g. "1 -> 2 -> 3 -> null".
    static String formatList(ListNode head) {
        List<String> parts = new ArrayList<>();
        for (ListNode node = head; node != null; node = node.next) parts.add(String.valueOf(node.val));
        parts.add("null");
        return String.join(" -> ", parts);
    }

    // Reverses the list in place and returns the new head (the old tail).
    static ListNode reverseList(ListNode head) {
        ListNode prev = null;
        ListNode curr = head;
        while (curr != null) {
            ListNode nextNode = curr.next; // Save the rest of the list before cutting the link.
            curr.next = prev; // Point this node back at the one before it.
            prev = curr;
            curr = nextNode;
        }
        return prev; // curr has walked off the end, so prev is the old tail.
    }

    public static void main(String[] args) {
        ListNode head = buildList(new int[] {1, 2, 3, 4, 5});
        System.out.println(formatList(head)); // 1 -> 2 -> 3 -> 4 -> 5 -> null
        System.out.println(formatList(reverseList(head))); // 5 -> 4 -> 3 -> 2 -> 1 -> null
        System.out.println(formatList(reverseList(buildList(new int[] {7})))); // 7 -> null
        System.out.println(formatList(reverseList(buildList(new int[] {})))); // null
    }
}

Worked Problems

Each solution stays folded until you open it, so you can try the problem first. Every one is a complete program in Python, TypeScript, Go, C# and Java, run and checked against its expected output.

Merge Two Sorted Lists

EASYLeetCode 21

Problem

You get the heads of two linked lists, each sorted in ascending order. Splice their nodes together into one sorted list and return its head. Reuse the existing nodes rather than creating new ones.

Example: 1 -> 2 -> 4 and 1 -> 3 -> 4 merge into 1 -> 1 -> 2 -> 3 -> 4 -> 4.

Show Solution

Approach

This is the merge step of merge sort, done with pointers. Compare the two heads, append the smaller one to the result, and advance that list. A dummy node in front of the result means there's always a tail to append to, so an empty result doesn't need its own branch. When one list runs out, the other is already sorted, so you attach the rest of it in one step.

Taking from a on a tie (<=) keeps equal values in their original order. That makes the merge stable, which matters when the nodes carry more than a number. With lists of length n and m, each node is appended once, and the only new node is the dummy.

Time O(n + m)Space O(1)

from __future__ import annotations


class ListNode:
    """One node of a singly linked list."""

    def __init__(self, val: int, next: ListNode | None = None):
        self.val = val
        self.next = next


def merge_two_lists(a: ListNode | None, b: ListNode | None) -> ListNode | None:
    """Merges two sorted lists into one sorted list by relinking their nodes."""
    # A placeholder in front of the result, so the first append isn't a special case.
    dummy = ListNode(0)
    tail = dummy
    while a is not None and b is not None:
        # Take the smaller head. On a tie take from a, so equal values keep their order.
        if a.val <= b.val:
            tail.next = a
            tail = a
            a = a.next
        else:
            tail.next = b
            tail = b
            b = b.next
    # One list ran out. The other is already sorted, so attach the rest of it whole.
    tail.next = a if a is not None else b
    return dummy.next


def build_list(values: list[int]) -> ListNode | None:
    head = None
    for value in reversed(values):
        head = ListNode(value, head)
    return head


def format_list(head: ListNode | None) -> str:
    parts = []
    node = head
    while node is not None:
        parts.append(str(node.val))
        node = node.next
    parts.append("None")
    return " -> ".join(parts)


if __name__ == "__main__":
    print(format_list(merge_two_lists(build_list([1, 2, 4]), build_list([1, 3, 4]))))  # 1 -> 1 -> 2 -> 3 -> 4 -> 4 -> None
    print(format_list(merge_two_lists(build_list([]), build_list([]))))  # None
    print(format_list(merge_two_lists(build_list([]), build_list([0]))))  # 0 -> None
// One node of a singly linked list.
export class ListNode {
  val: number;
  next: ListNode | null;

  constructor(val: number, next: ListNode | null = null) {
    this.val = val;
    this.next = next;
  }
}

// Merges two sorted lists into one sorted list by relinking their nodes.
export function mergeTwoLists(a: ListNode | null, b: ListNode | null): ListNode | null {
  // A placeholder in front of the result, so the first append isn't a special case.
  const dummy = new ListNode(0);
  let tail = dummy;
  while (a !== null && b !== null) {
    // Take the smaller head. On a tie take from a, so equal values keep their order.
    if (a.val <= b.val) {
      tail.next = a;
      tail = a;
      a = a.next;
    } else {
      tail.next = b;
      tail = b;
      b = b.next;
    }
  }
  // One list ran out. The other is already sorted, so attach the rest of it whole.
  tail.next = a ?? b;
  return dummy.next;
}

function buildList(values: number[]): ListNode | null {
  let head: ListNode | null = null;
  for (let i = values.length - 1; i >= 0; i--) head = new ListNode(values[i], head);
  return head;
}

function formatList(head: ListNode | null): string {
  const parts: string[] = [];
  for (let node = head; node !== null; node = node.next) parts.push(String(node.val));
  parts.push("null");
  return parts.join(" -> ");
}

console.log(formatList(mergeTwoLists(buildList([1, 2, 4]), buildList([1, 3, 4])))); // 1 -> 1 -> 2 -> 3 -> 4 -> 4 -> null
console.log(formatList(mergeTwoLists(buildList([]), buildList([])))); // null
console.log(formatList(mergeTwoLists(buildList([]), buildList([0])))); // 0 -> null
package main

import (
	"fmt"
	"strconv"
	"strings"
)

// ListNode is one node of a singly linked list.
type ListNode struct {
	Val  int
	Next *ListNode
}

// mergeTwoLists merges two sorted lists into one sorted list by relinking
// their nodes.
func mergeTwoLists(a, b *ListNode) *ListNode {
	// A placeholder in front of the result, so the first append isn't a special case.
	dummy := &ListNode{}
	tail := dummy
	for a != nil && b != nil {
		// Take the smaller head. On a tie take from a, so equal values keep their order.
		if a.Val <= b.Val {
			tail.Next = a
			tail = a
			a = a.Next
		} else {
			tail.Next = b
			tail = b
			b = b.Next
		}
	}
	// One list ran out. The other is already sorted, so attach the rest of it whole.
	if a != nil {
		tail.Next = a
	} else {
		tail.Next = b
	}
	return dummy.Next
}

func buildList(values []int) *ListNode {
	var head *ListNode
	for i := len(values) - 1; i >= 0; i-- {
		head = &ListNode{Val: values[i], Next: head}
	}
	return head
}

func formatList(head *ListNode) string {
	parts := []string{}
	for node := head; node != nil; node = node.Next {
		parts = append(parts, strconv.Itoa(node.Val))
	}
	parts = append(parts, "nil")
	return strings.Join(parts, " -> ")
}

func main() {
	fmt.Println(formatList(mergeTwoLists(buildList([]int{1, 2, 4}), buildList([]int{1, 3, 4})))) // 1 -> 1 -> 2 -> 3 -> 4 -> 4 -> nil
	fmt.Println(formatList(mergeTwoLists(buildList([]int{}), buildList([]int{}))))               // nil
	fmt.Println(formatList(mergeTwoLists(buildList([]int{}), buildList([]int{0}))))              // 0 -> nil
}
using System;
using System.Collections.Generic;

public class Solution
{
    // One node of a singly linked list.
    public class ListNode
    {
        public int Val;
        public ListNode? Next;

        public ListNode(int val, ListNode? next = null)
        {
            Val = val;
            Next = next;
        }
    }

    // Merges two sorted lists into one sorted list by relinking their nodes.
    public static ListNode? MergeTwoLists(ListNode? a, ListNode? b)
    {
        // A placeholder in front of the result, so the first append isn't a special case.
        var dummy = new ListNode(0);
        var tail = dummy;
        while (a != null && b != null)
        {
            // Take the smaller head. On a tie take from a, so equal values keep their order.
            if (a.Val <= b.Val)
            {
                tail.Next = a;
                tail = a;
                a = a.Next;
            }
            else
            {
                tail.Next = b;
                tail = b;
                b = b.Next;
            }
        }
        // One list ran out. The other is already sorted, so attach the rest of it whole.
        tail.Next = a ?? b;
        return dummy.Next;
    }

    static ListNode? BuildList(int[] values)
    {
        ListNode? head = null;
        for (int i = values.Length - 1; i >= 0; i--) head = new ListNode(values[i], head);
        return head;
    }

    static string FormatList(ListNode? head)
    {
        var parts = new List<string>();
        for (var node = head; node != null; node = node.Next) parts.Add(node.Val.ToString());
        parts.Add("null");
        return string.Join(" -> ", parts);
    }

    public static void Main()
    {
        Console.WriteLine(FormatList(MergeTwoLists(BuildList(new[] { 1, 2, 4 }), BuildList(new[] { 1, 3, 4 })))); // 1 -> 1 -> 2 -> 3 -> 4 -> 4 -> null
        Console.WriteLine(FormatList(MergeTwoLists(BuildList(new int[0]), BuildList(new int[0])))); // null
        Console.WriteLine(FormatList(MergeTwoLists(BuildList(new int[0]), BuildList(new[] { 0 })))); // 0 -> null
    }
}
import java.util.ArrayList;
import java.util.List;

public class Solution {
    // One node of a singly linked list.
    static class ListNode {
        int val;
        ListNode next;

        ListNode(int val, ListNode next) {
            this.val = val;
            this.next = next;
        }
    }

    // Merges two sorted lists into one sorted list by relinking their nodes.
    static ListNode mergeTwoLists(ListNode a, ListNode b) {
        // A placeholder in front of the result, so the first append isn't a special case.
        ListNode dummy = new ListNode(0, null);
        ListNode tail = dummy;
        while (a != null && b != null) {
            // Take the smaller head. On a tie take from a, so equal values keep their order.
            if (a.val <= b.val) {
                tail.next = a;
                tail = a;
                a = a.next;
            } else {
                tail.next = b;
                tail = b;
                b = b.next;
            }
        }
        // One list ran out. The other is already sorted, so attach the rest of it whole.
        tail.next = a != null ? a : b;
        return dummy.next;
    }

    static ListNode buildList(int[] values) {
        ListNode head = null;
        for (int i = values.length - 1; i >= 0; i--) head = new ListNode(values[i], head);
        return head;
    }

    static String formatList(ListNode head) {
        List<String> parts = new ArrayList<>();
        for (ListNode node = head; node != null; node = node.next) parts.add(String.valueOf(node.val));
        parts.add("null");
        return String.join(" -> ", parts);
    }

    public static void main(String[] args) {
        System.out.println(formatList(mergeTwoLists(buildList(new int[] {1, 2, 4}), buildList(new int[] {1, 3, 4})))); // 1 -> 1 -> 2 -> 3 -> 4 -> 4 -> null
        System.out.println(formatList(mergeTwoLists(buildList(new int[] {}), buildList(new int[] {})))); // null
        System.out.println(formatList(mergeTwoLists(buildList(new int[] {}), buildList(new int[] {0})))); // 0 -> null
    }
}

Linked List Cycle

EASYLeetCode 141

Problem

Given the head of a linked list, return whether it has a cycle: a node whose next pointer leads back to a node you've already passed, so walking the list never ends.

Example: in 3 -> 2 -> 0 -> -4, where -4 points back to 2, the answer is true. LeetCode writes that list as head = [3, 2, 0, -4], pos = 1, where pos is the index the tail links back to, or -1 for no cycle. The demo builds its lists the same way.

Show Solution

Approach

The easy answer is a hash set of visited nodes: see one twice and there's a cycle. That costs O(n) extra space. Floyd's algorithm does it in O(1). Move slow one node per step and fast two. Without a cycle, fast reaches the end and you return false.

With a cycle, both pointers end up going around it. From then on, fast gains exactly one node on slow per step, so the gap between them shrinks by one each time. It can't jump over slow, because the gap only ever drops by one, so they meet within one lap. slow enters the cycle within n steps and they meet less than one lap later, which keeps the whole thing O(n).

3 -> 2 -> 0 -> -4
     ^         |
     +---------+

step  slow  fast
0     3     3
1     2     0
2     0     2
3     -4    -4    same node
fast laps around the cycle and lands on slow after three steps.

Fast and Slow Pointers Meet in the Cycle

Step 1 of 4

  • Where slow and fast stand
  • Where they meet

Both pointers start at the head, 3. The tail -4 links back to 2, so following next never reaches null.

All 4 steps as text
  1. Both pointers start at the head, 3. The tail -4 links back to 2, so following next never reaches null.
  2. slow moves one node to 2 and fast two to 0. Both are inside the cycle now, and fast is 2 nodes behind slow.
  3. slow moves one node to 0 and fast two to 2. fast gains one node per step, so the gap drops from 2 to 1. It can't jump over slow.
  4. slow moves to -4 and fast lands there too, closing the last gap. They're on the same node, so the list has a cycle: return true.

Time O(n)Space O(1)

from __future__ import annotations


class ListNode:
    """One node of a singly linked list."""

    def __init__(self, val: int, next: ListNode | None = None):
        self.val = val
        self.next = next


def has_cycle(head: ListNode | None) -> bool:
    """Whether following next pointers from head ever comes back to a node it has
    already passed. Floyd's fast and slow pointers use O(1) extra space."""
    slow = head
    fast = head
    while fast is not None and fast.next is not None:
        slow = slow.next  # One step. slow trails fast, so it isn't None here.
        fast = fast.next.next  # Two steps.
        # In a cycle, fast closes the gap by one node per step, so it can't skip slow.
        if slow is fast:
            return True
    return False  # fast reached the end, so there's no cycle.


def build_with_cycle(values: list[int], pos: int) -> ListNode | None:
    """Builds a list from values. If pos isn't -1, the tail links back to the node
    at index pos, which makes a cycle."""
    nodes = [ListNode(value) for value in values]
    for i in range(len(nodes) - 1):
        nodes[i].next = nodes[i + 1]
    if pos >= 0:
        nodes[-1].next = nodes[pos]  # Close the loop.
    return nodes[0] if nodes else None


if __name__ == "__main__":
    print(has_cycle(build_with_cycle([3, 2, 0, -4], 1)))  # True
    print(has_cycle(build_with_cycle([1, 2], 0)))  # True
    print(has_cycle(build_with_cycle([1], -1)))  # False
    print(has_cycle(build_with_cycle([], -1)))  # False
// One node of a singly linked list.
export class ListNode {
  val: number;
  next: ListNode | null;

  constructor(val: number, next: ListNode | null = null) {
    this.val = val;
    this.next = next;
  }
}

// Whether following next pointers from head ever comes back to a node it has
// already passed. Floyd's fast and slow pointers use O(1) extra space.
export function hasCycle(head: ListNode | null): boolean {
  let slow = head;
  let fast = head;
  while (fast !== null && fast.next !== null) {
    slow = slow!.next; // One step. slow trails fast, so it isn't null here.
    fast = fast.next.next; // Two steps.
    // In a cycle, fast closes the gap by one node per step, so it can't skip slow.
    if (slow === fast) return true;
  }
  return false; // fast reached the end, so there's no cycle.
}

// Builds a list from values. If pos isn't -1, the tail links back to the node
// at index pos, which makes a cycle.
function buildWithCycle(values: number[], pos: number): ListNode | null {
  const nodes = values.map((value) => new ListNode(value));
  for (let i = 0; i + 1 < nodes.length; i++) nodes[i].next = nodes[i + 1];
  if (pos >= 0) nodes[nodes.length - 1].next = nodes[pos]; // Close the loop.
  return nodes.length > 0 ? nodes[0] : null;
}

console.log(hasCycle(buildWithCycle([3, 2, 0, -4], 1))); // true
console.log(hasCycle(buildWithCycle([1, 2], 0))); // true
console.log(hasCycle(buildWithCycle([1], -1))); // false
console.log(hasCycle(buildWithCycle([], -1))); // false
package main

import "fmt"

// ListNode is one node of a singly linked list.
type ListNode struct {
	Val  int
	Next *ListNode
}

// hasCycle reports whether following Next pointers from head ever comes back
// to a node it has already passed. Floyd's fast and slow pointers use O(1)
// extra space.
func hasCycle(head *ListNode) bool {
	slow, fast := head, head
	for fast != nil && fast.Next != nil {
		slow = slow.Next      // One step. slow trails fast, so it isn't nil here.
		fast = fast.Next.Next // Two steps.
		// In a cycle, fast closes the gap by one node per step, so it can't skip slow.
		if slow == fast {
			return true
		}
	}
	return false // fast reached the end, so there's no cycle.
}

// buildWithCycle builds a list from values. If pos isn't -1, the tail links
// back to the node at index pos, which makes a cycle.
func buildWithCycle(values []int, pos int) *ListNode {
	nodes := make([]*ListNode, len(values))
	for i, value := range values {
		nodes[i] = &ListNode{Val: value}
	}
	for i := 0; i+1 < len(nodes); i++ {
		nodes[i].Next = nodes[i+1]
	}
	if pos >= 0 {
		nodes[len(nodes)-1].Next = nodes[pos] // Close the loop.
	}
	if len(nodes) == 0 {
		return nil
	}
	return nodes[0]
}

func main() {
	fmt.Println(hasCycle(buildWithCycle([]int{3, 2, 0, -4}, 1))) // true
	fmt.Println(hasCycle(buildWithCycle([]int{1, 2}, 0)))        // true
	fmt.Println(hasCycle(buildWithCycle([]int{1}, -1)))          // false
	fmt.Println(hasCycle(buildWithCycle([]int{}, -1)))           // false
}
using System;
using System.Linq;

public class Solution
{
    // One node of a singly linked list.
    public class ListNode
    {
        public int Val;
        public ListNode? Next;

        public ListNode(int val, ListNode? next = null)
        {
            Val = val;
            Next = next;
        }
    }

    // Whether following Next pointers from head ever comes back to a node it has
    // already passed. Floyd's fast and slow pointers use O(1) extra space.
    public static bool HasCycle(ListNode? head)
    {
        var slow = head;
        var fast = head;
        while (fast != null && fast.Next != null)
        {
            slow = slow!.Next; // One step. slow trails fast, so it isn't null here.
            fast = fast.Next.Next; // Two steps.
            // In a cycle, fast closes the gap by one node per step, so it can't skip slow.
            if (slow == fast) return true;
        }
        return false; // fast reached the end, so there's no cycle.
    }

    // Builds a list from values. If pos isn't -1, the tail links back to the node
    // at index pos, which makes a cycle.
    static ListNode? BuildWithCycle(int[] values, int pos)
    {
        var nodes = values.Select(value => new ListNode(value)).ToArray();
        for (int i = 0; i + 1 < nodes.Length; i++) nodes[i].Next = nodes[i + 1];
        if (pos >= 0) nodes[^1].Next = nodes[pos]; // Close the loop.
        return nodes.Length > 0 ? nodes[0] : null;
    }

    public static void Main()
    {
        Console.WriteLine(HasCycle(BuildWithCycle(new[] { 3, 2, 0, -4 }, 1))); // True
        Console.WriteLine(HasCycle(BuildWithCycle(new[] { 1, 2 }, 0))); // True
        Console.WriteLine(HasCycle(BuildWithCycle(new[] { 1 }, -1))); // False
        Console.WriteLine(HasCycle(BuildWithCycle(new int[0], -1))); // False
    }
}
public class Solution {
    // One node of a singly linked list.
    static class ListNode {
        int val;
        ListNode next;

        ListNode(int val, ListNode next) {
            this.val = val;
            this.next = next;
        }
    }

    // Whether following next pointers from head ever comes back to a node it has
    // already passed. Floyd's fast and slow pointers use O(1) extra space.
    static boolean hasCycle(ListNode head) {
        ListNode slow = head;
        ListNode fast = head;
        while (fast != null && fast.next != null) {
            slow = slow.next; // One step. slow trails fast, so it isn't null here.
            fast = fast.next.next; // Two steps.
            // In a cycle, fast closes the gap by one node per step, so it can't skip slow.
            if (slow == fast) return true;
        }
        return false; // fast reached the end, so there's no cycle.
    }

    // Builds a list from values. If pos isn't -1, the tail links back to the node
    // at index pos, which makes a cycle.
    static ListNode buildWithCycle(int[] values, int pos) {
        ListNode[] nodes = new ListNode[values.length];
        for (int i = 0; i < values.length; i++) nodes[i] = new ListNode(values[i], null);
        for (int i = 0; i + 1 < nodes.length; i++) nodes[i].next = nodes[i + 1];
        if (pos >= 0) nodes[nodes.length - 1].next = nodes[pos]; // Close the loop.
        return nodes.length > 0 ? nodes[0] : null;
    }

    public static void main(String[] args) {
        System.out.println(hasCycle(buildWithCycle(new int[] {3, 2, 0, -4}, 1))); // true
        System.out.println(hasCycle(buildWithCycle(new int[] {1, 2}, 0))); // true
        System.out.println(hasCycle(buildWithCycle(new int[] {1}, -1))); // false
        System.out.println(hasCycle(buildWithCycle(new int[] {}, -1))); // false
    }
}

Common Pitfalls

  • Overwriting curr.next before saving it. In a reversal, store the next node first, or the rest of the list is gone.
  • Returning the old head after reversing. It's now the tail, and the new head is prev.
  • Following fast.next.next without checking that fast and fast.next exist. Any list without a cycle will hit null.
  • Special-casing the first node by hand instead of using a dummy, then getting the empty or one-node list wrong.
  • Comparing node values instead of the nodes themselves in cycle detection. Two different nodes can hold the same value.

Practice

More problems to try on your own.