← Back to DSA Course | Chapter 4: Linked Lists | Lesson 7 of 8

Find Middle of Linked List

Problem

Finding the middle node of a linked list comes up as a building block in several other algorithms, like splitting a list in half for merge sort or checking whether a list is a palindrome.

Example: Problem

#include <iostream>
using namespace std;
struct Node { int data; Node* next; };
int main() {
    Node* a = new Node{1, new Node{2, new Node{3, new Node{4, new Node{5, nullptr}}}}};
    cout << "finding the middle helps split a list for merge sort or palindrome checks" << endl;
    return 0;
}
class Node { int data; Node next; Node(int d) { data = d; } }
public class Main {
    public static void main(String[] args) {
        Node a = new Node(1); a.next = new Node(2); a.next.next = new Node(3);
        System.out.println("finding the middle helps split a list for merge sort or palindrome checks");
    }
}
class Node:
    def __init__(self, data):
        self.data = data
        self.next = None

a = Node(1); a.next = Node(2); a.next.next = Node(3)
print("finding the middle helps split a list for merge sort or palindrome checks")
#include <stdio.h>
int main() {
    printf("finding the middle helps split a list for merge sort or palindrome checks\n");
    return 0;
}

Slow and Fast

The efficient approach uses two pointers moving at different speeds: a slow pointer that advances one node per step, and a fast pointer that advances two nodes per step, both starting from the head.

Example: Slow and Fast

#include <iostream>
using namespace std;
struct Node { int data; Node* next; };
int main() {
    Node* head = new Node{1, new Node{2, new Node{3, new Node{4, new Node{5, nullptr}}}}};
    Node* slow = head; Node* fast = head;
    cout << "slow starts at " << slow->data << ", fast starts at " << fast->data << endl;
    return 0;
}
class Node { int data; Node next; Node(int d) { data = d; } }
public class Main {
    public static void main(String[] args) {
        Node head = new Node(1); head.next = new Node(2);
        Node slow = head, fast = head;
        System.out.println("slow starts at " + slow.data + ", fast starts at " + fast.data);
    }
}
class Node:
    def __init__(self, data):
        self.data = data
        self.next = None

head = Node(1); head.next = Node(2)
slow = fast = head
print("slow starts at", slow.data, ", fast starts at", fast.data)
#include <stdio.h>
#include <stdlib.h>
struct Node { int data; struct Node *next; };
int main() {
    struct Node *head = malloc(sizeof(struct Node)); head->data = 1;
    struct Node *slow = head, *fast = head;
    printf("slow starts at %d, fast starts at %d\n", slow->data, fast->data);
    return 0;
}

Finding the Middle

By the time the fast pointer reaches the end of the list, the slow pointer, having covered exactly half the distance, is sitting right at the middle node, no separate length calculation needed.

Example: Finding the Middle

#include <iostream>
using namespace std;
struct Node { int data; Node* next; };
int main() {
    Node* head = new Node{1, new Node{2, new Node{3, new Node{4, new Node{5, nullptr}}}}};
    Node* slow = head; Node* fast = head;
    while (fast && fast->next) { slow = slow->next; fast = fast->next->next; }
    cout << "middle node: " << slow->data << endl;
    return 0;
}
class Node { int data; Node next; Node(int d) { data = d; } }
public class Main {
    public static void main(String[] args) {
        Node head = new Node(1); head.next = new Node(2); head.next.next = new Node(3);
        head.next.next.next = new Node(4); head.next.next.next.next = new Node(5);
        Node slow = head, fast = head;
        while (fast != null && fast.next != null) { slow = slow.next; fast = fast.next.next; }
        System.out.println("middle node: " + slow.data);
    }
}
class Node:
    def __init__(self, data):
        self.data = data
        self.next = None

head = Node(1)
head.next = Node(2); head.next.next = Node(3)
head.next.next.next = Node(4); head.next.next.next.next = Node(5)
slow = fast = head
while fast and fast.next:
    slow = slow.next
    fast = fast.next.next
print("middle node:", slow.data)
#include <stdio.h>
#include <stdlib.h>
struct Node { int data; struct Node *next; };
int main() {
    struct Node *n5 = malloc(sizeof(struct Node)); n5->data = 5; n5->next = NULL;
    struct Node *n4 = malloc(sizeof(struct Node)); n4->data = 4; n4->next = n5;
    struct Node *n3 = malloc(sizeof(struct Node)); n3->data = 3; n3->next = n4;
    struct Node *n2 = malloc(sizeof(struct Node)); n2->data = 2; n2->next = n3;
    struct Node *head = malloc(sizeof(struct Node)); head->data = 1; head->next = n2;
    struct Node *slow = head, *fast = head;
    while (fast && fast->next) { slow = slow->next; fast = fast->next->next; }
    printf("middle node: %d\n", slow->data);
    return 0;
}

Why It Works

This works because the fast pointer always covers twice the distance the slow pointer does in the same number of steps, so when fast has traveled the full length, slow has traveled exactly half of it.

Example: Why It Works

#include <iostream>
using namespace std;
int main() {
    int steps = 0;
    for (int fastPos = 0; fastPos < 5; fastPos += 2) steps++;
    cout << "fast moves 2x speed of slow, so when fast covers full length, slow is at half: " << steps << " steps" << endl;
    return 0;
}
public class Main {
    public static void main(String[] args) {
        int steps = 0;
        for (int fastPos = 0; fastPos < 5; fastPos += 2) steps++;
        System.out.println("fast moves 2x speed of slow, so when fast covers full length, slow is at half: " + steps + " steps");
    }
}
steps = 0
fast_pos = 0
while fast_pos < 5:
    fast_pos += 2
    steps += 1
print("fast moves 2x speed of slow, so when fast covers full length, slow is at half:", steps, "steps")
#include <stdio.h>
int main() {
    int steps = 0;
    for (int fastPos = 0; fastPos < 5; fastPos += 2) steps++;
    printf("fast moves 2x speed, slow ends at half: %d steps\n", steps);
    return 0;
}

Complexity

This slow/fast pointer approach finds the middle in a single O(n) pass through the list, using only O(1) extra space, compared to a two-pass approach that would first count the length and then walk to the midpoint.

Example: Complexity

#include <iostream>
using namespace std;
struct Node { int data; Node* next; };
int main() {
    Node* head = new Node{1, new Node{2, new Node{3, nullptr}}};
    Node* slow = head; Node* fast = head;
    while (fast && fast->next) { slow = slow->next; fast = fast->next->next; }
    cout << "found middle (" << slow->data << ") in one O(n) pass, O(1) space" << endl;
    return 0;
}
class Node { int data; Node next; Node(int d) { data = d; } }
public class Main {
    public static void main(String[] args) {
        Node head = new Node(1); head.next = new Node(2); head.next.next = new Node(3);
        Node slow = head, fast = head;
        while (fast != null && fast.next != null) { slow = slow.next; fast = fast.next.next; }
        System.out.println("found middle (" + slow.data + ") in one O(n) pass, O(1) space");
    }
}
class Node:
    def __init__(self, data):
        self.data = data
        self.next = None

head = Node(1); head.next = Node(2); head.next.next = Node(3)
slow = fast = head
while fast and fast.next:
    slow = slow.next
    fast = fast.next.next
print("found middle (", slow.data, ") in one O(n) pass, O(1) space")
#include <stdio.h>
#include <stdlib.h>
struct Node { int data; struct Node *next; };
int main() {
    struct Node *n3 = malloc(sizeof(struct Node)); n3->data = 3; n3->next = NULL;
    struct Node *n2 = malloc(sizeof(struct Node)); n2->data = 2; n2->next = n3;
    struct Node *head = malloc(sizeof(struct Node)); head->data = 1; head->next = n2;
    struct Node *slow = head, *fast = head;
    while (fast && fast->next) { slow = slow->next; fast = fast->next->next; }
    printf("found middle (%d) in O(n) pass, O(1) space\n", slow->data);
    return 0;
}

Login to run this code

C/C++/Java/PHP execution requires a free account. Your code is saved — you'll land right back in the editor after logging in.