Linked List in Python: A Complete Guide to Implementation, Types, and Real-World Projects

Linked List in Python A Complete Guide to Implementation, Types, and Real-World Projects

When you first start learning Python data structures, built-in lists, dictionaries, and tuples tend to get all the attention. Linked lists sit quietly in the background – underused, often skipped in introductory courses, and rarely understood at depth until a technical interview forces the question.

That gap is worth closing. A linked list in Python is one of the most foundational data structures in computer science, and understanding it deeply changes how you think about memory, performance, and dynamic data management. Unlike Python’s built-in list, which stores elements in a contiguous memory block, a linked list stores each element as an independent node that carries a reference to the next element in the sequence. That small architectural difference unlocks behaviors around insertion and deletion that Python lists simply cannot match at the same performance level.

This guide covers everything you need to learn linked list in Python – from basic node structure to singly, doubly, and circular variants, reversing a linked list, deleting nodes, and a full real-world project: a music playlist manager built entirely with linked list logic.

A Linked List in Python is a linear data structure where each node stores data and a reference to the next node, allowing dynamic memory allocation and efficient insertions or deletions. Unlike Python lists, linked lists do not require contiguous memory and are ideal for frequently changing data. Common types include singly, doubly, and circular linked lists. This guide covers implementation, operations, performance comparisons, and a real-world playlist manager project built using linked list concepts.

What Is a Linked List in Python?

A linked list is an ordered collection of nodes. Each node holds two things:

  • Data – the value stored at that position
  • Next – a reference (pointer) to the next node in the sequence

The list begins at a node called the head. Traversal continues node by node until a node’s next value is None, which marks the end of the list. There is no index-based random access like Python lists provide. You always start at the head and walk forward.

This structure makes linked lists exceptionally efficient for insertions and deletions near the beginning of a collection. In a standard Python list, inserting at position zero forces every element to shift one slot, an O(n) operation. In a linked list, you rewire a single pointer – O(1).

registor_now_P

How to Create a Linked List in Python

Learning how to implement a linked list in Python starts with two classes: one for the individual node, and one for the list container.

The Node Class

python

class Node:

    def __init__(self, data):

        self.data = data

        self.next = None

    def __repr__(self):

        return str(self.data)

The LinkedList Class

python

class LinkedList:

    def __init__(self, nodes=None):

        self.head = None

        if nodes is not None:

            node = Node(data=nodes.pop(0))

            self.head = node

            for elem in nodes:

                node.next = Node(data=elem)

                node = node.next

    def __repr__(self):

        node = self.head

        nodes = []

        while node is not None:

            nodes.append(str(node.data))

            node = node.next

        nodes.append("None")

        return " -> ".join(nodes)

    def __iter__(self):

        node = self.head

        while node is not None:

            yield node

            node = node.next

With these two classes, you have a working linked list program in Python that supports initialization and traversal. Creating a sample list looks like this:

python

llist = LinkedList(["a", "b", "c", "d", "e"])

print(llist)  # a -> b -> c -> d -> e -> None

Inserting Nodes into a Linked List

There are three main insertion patterns in a linked list implementation in Python: at the beginning, at the end, and between existing nodes.

Insert at the Beginning

python

def add_first(self, node):

    node.next = self.head

    self.head = node

No traversal needed. You update the head reference and the operation completes in O(1).

Insert at the End

python

def add_last(self, node):

    if self.head is None:

        self.head = node

        return

    for current_node in self:

        pass

    current_node.next = node

This requires traversing the entire list to reach the tail – O(n).

Insert After a Specific Node

python

def add_after(self, target_data, new_node):

    for node in self:

        if node.data == target_data:

            new_node.next = node.next

            node.next = new_node

            return

    raise Exception(f"Node '{target_data}' not found")

Insert Before a Specific Node

python

def add_before(self, target_data, new_node):

    if self.head is None:

        raise Exception("List is empty")

    if self.head.data == target_data:

        return self.add_first(new_node)

    prev_node = self.head

    for node in self:

        if node.data == target_data:

            prev_node.next = new_node

            new_node.next = node

            return

        prev_node = node

    raise Exception(f"Node '{target_data}' not found")

How to Delete a Node in a Linked List in Python

Deleting a node means finding the target, then relinking the node before it to point directly to the node after it, effectively removing the target from the chain.

python

def remove_node(self, target_data):

    if self.head is None:

        raise Exception("List is empty")

    if self.head.data == target_data:

        self.head = self.head.next

        return

    previous_node = self.head

    for node in self:

        if node.data == target_data:

            previous_node.next = node.next

            return

        previous_node = node

    raise Exception(f"Node '{target_data}' not found")

The essential technique for delete node in linked list Python is maintaining the previous_node reference throughout traversal. Without it, once you find and remove the target, you cannot re-link the surrounding nodes.

Singly Linked List in Python

Everything covered so far describes a singly linked list in Python, the most basic form. Each node points only forward. Traversal is one-directional, from head to tail.

Best suited for:

  • FIFO queues where elements enter at the rear and leave from the front
  • Stacks where elements are pushed and popped from the same end
  • Simple sequential processing where backward traversal is never needed

Doubly Linked List in Python

A doubly linked list in Python extends each node with a previous pointer in addition to next. This bidirectional structure enables traversal in both directions and makes tail operations constant time.

python

class Node:

    def __init__(self, data):

        self.data = data

        self.next = None

        self.previous = None

Python’s collections.deque is built on this structure internally, which is precisely why it delivers O(1) performance when appending or popping from both ends. If you have used deque for queue or stack implementations, you were already benefiting from a doubly linked list.

Best suited for:

  • Browser history (forward and backward navigation)
  • Undo/redo functionality in text editors
  • Any system where you need to move in both directions through a sequence

Circular Linked List in Python

A circular linked list in Python differs from a singly linked list in one critical way: the last node points back to the head instead of None. The chain has no end, it loops continuously.

python

class CircularLinkedList:

    def __init__(self):

        self.head = None

    def traverse(self, starting_point=None):

        if starting_point is None:

            starting_point = self.head

        node = starting_point

        while node is not None and (node.next != starting_point):

            yield node

            node = node.next

        yield node

    def print_list(self, starting_point=None):

        nodes = []

        for node in self.traverse(starting_point):

            nodes.append(str(node))

        print(" -> ".join(nodes))

One critical implementation detail: because there is no None terminator, you must stop traversal when the current node’s next equals your starting point – not when it equals None. Missing this causes an infinite loop.

Real-world applications:

  • Turn-based multiplayer games (cycling through players indefinitely)
  • Operating system round-robin CPU scheduling
  • Media players with “repeat all” enabled
  • Fibonacci heap implementation

How to Reverse a Linked List in Python

Reversing a linked list is one of the most common technical interview problems involving this data structure. The iterative solution is clean, runs in O(n) time, and requires only O(1) extra space.

python

def reverse(self):

    prev = None

    current = self.head

    while current is not None:

        next_node = current.next

        current.next = prev

        prev = current

        current = next_node

    self.head = prev

Step-by-step walkthrough:

  1. Initialize prev = None and current = self.head
  2. Save current.next into next_node before overwriting it
  3. Flip current.next to point backward toward prev
  4. Advance prev to current, and current to next_node
  5. Repeat until current is None – at that point prev is the new head

The most common mistake here is forgetting step 2. If you overwrite current.next without saving it first, you lose the rest of the list permanently.

 

Explore Courses - Learn More

Performance Comparison: Python List vs Linked List

Operation

Python List

Singly Linked List

Doubly Linked List

Insert at beginning

O(n)

O(1)

O(1)

Insert at end

O(1)

O(n)

O(1)

Delete at beginning

O(n)

O(1)

O(1)

Delete at end

O(n)

O(n)

O(1)

Access by index

O(1)

O(n)

O(n)

Search by value

O(n)

O(n)

O(n)

The takeaway is clear. When your application frequently inserts or removes elements near the front of a collection — as in a queue — a linked list significantly outperforms a standard Python list. For index-based random access, Python’s built-in list wins decisively.

Creating a Playlist with Linked Lists in Python

One of the most intuitive real-world applications of a linked list program in Python is a music playlist manager. Songs can be added at any position, removed by name, shuffled, and loaded from external files, all without the element-shifting overhead that a regular Python list would impose.

This project uses three standard Python modules alongside the linked list structure:

  • time: introduces playback delays to simulate song duration
  • random: enables shuffle functionality
  • csv: allows importing a song library from an external file

Song and Node Classes

python

import time

import random

import csv

class Song:

    def __init__(self, song_id, song_name, song_length):

        self.song_id = song_id

        self.song_name = song_name

        self.song_length = song_length

    def __str__(self):

        return str({

            'song_id': self.song_id,

            'song_name': self.song_name,

            'song_length': self.song_length

        })

class ListNode:

    def __init__(self, song: Song):

        self.song = song

        self.next = None

    def __str__(self):

        return str(self.song)

The Playlist LinkedList Class

python

class LinkedList:

    def __init__(self):

        self.head_node = None

        self.count = 0

    def traversal(self):

        if self.head_node is None:

            return

        temp_node = self.head_node

        while temp_node is not None:

            print(temp_node.song)

            time.sleep(2)

            temp_node = temp_node.next

    def insert_at_start(self, node):

        if self.head_node is None:

            self.head_node = node

            self.count += 1

            return True

        node.next = self.head_node

        self.head_node = node

        return True

    def insert_after(self, song_name, node):

        temp_node = self.head_node

        while temp_node and temp_node.song.song_name != song_name:

            temp_node = temp_node.next

        if temp_node is None:

            return False

        if temp_node.next is None:

            temp_node.next = node

        else:

            node.next = temp_node.next

            temp_node.next = node

        return True

    def insert_before(self, song_name, node):

        temp_node = self.head_node

        prev_node = None

        while temp_node and temp_node.song.song_name != song_name:

            prev_node = temp_node

            temp_node = temp_node.next

        if temp_node is None:

            return False

        if prev_node is None:

            node.next = self.head_node

            self.head_node = node

            return True

        prev_node.next = node

        node.next = temp_node

        return True

    def delete_song(self, song_name):

        curr = self.head_node

        if curr.song.song_name == song_name:

            self.head_node = curr.next

            del curr

            return

        while curr is not None:

            if curr.next and curr.next.song.song_name == song_name:

                break

            curr = curr.next

        if curr is None:

            raise Exception("Song not found in playlist")

        temp = curr.next

        curr.next = curr.next.next

        del temp

Loading Songs from a CSV File

python

def create_linked_list(filepath):

    with open(filepath, newline='') as csvfile:

        reader = csv.DictReader(csvfile)

        linked_list = LinkedList()

        for row in reader:

            song = Song(row['Song ID'], row['Song Name'], row['Song Length'])

            listnode = ListNode(song)

            linked_list.insert_at_start(listnode)

    return linked_list

Why a Linked List Makes Sense for a Playlist

A standard Python list requires shifting every subsequent element each time a song is inserted at an arbitrary position. With a linked list, inserting a song between two existing tracks is just a pointer update – O(1) for the rewiring once the target position is found. For a dynamic playlist that changes frequently, tracks added mid-queue, songs skipped, the order rearranged on the fly, a linked list provides both clean logic and efficient performance.

Common Mistakes When Working with Linked Lists in Python

Even experienced developers make avoidable errors when working with linked list examples in Python. These are the patterns to watch:

Not checking for an empty list. Every method that modifies the list should verify that self.head is not None before attempting pointer operations.

Losing the previous node reference during deletion. You must track the predecessor at every step. Losing it means you cannot re-link after removing the target node.

Infinite loops in circular linked lists. Always check whether node.next == starting_point, not whether node.next is None. There is no tail in a circular structure.

Overwriting next before saving it during reversal. This permanently severs access to the rest of the list. Always store current.next in a temporary variable first.

When to Use a Linked List vs Python’s Built-in List

Use a linked list when:

  • You need O(1) insertions or deletions at the beginning of a collection
  • You are implementing a FIFO queue and performance at both ends matters
  • You are building features like undo stacks, browser history, or turn-based game logic
  • You need bidirectional traversal (choose a doubly linked list)

Stick with Python’s list when:

  • You need fast O(1) random access by index
  • The collection is mostly read-heavy with rare structural changes
  • Simplicity and built-in method support outweigh performance edge cases

 

Talk to Academic Advisor

Conclusion

A linked list in Python is more than a data structures textbook topic. It is a practical tool with real applications in queues, stacks, graph adjacency representations, operating system process management, and dynamic media applications like playlist managers.

Understanding how to implement a linked list in Python, and knowing when to choose between singly, doubly, and circular variants, sharpens your thinking about performance trade-offs and memory design. Practice building these from scratch, apply them to a real project like the playlist example above, and the concepts will solidify quickly. Once you have that foundation, you will start recognizing scenarios in your own work where a linked list is exactly the right structure for the job.

FAQ

A linked list is a data structure where each element (called a node) stores a data value and a reference to the next node. Unlike Python lists, which are contiguous arrays, linked lists connect nodes through pointers stored within each node itself.

Use a linked list when your workload involves frequent insertions or deletions at the beginning or middle of the collection. If you primarily access elements by index or append to the end, a Python list is faster and simpler.

Yes. Internally, collections.deque is implemented as a doubly linked list. It delivers O(1) performance for appends and pops from both ends, which is precisely the linked list advantage.

Create a Node class with data and next attributes. Build a LinkedList class that stores the head node and implements methods for traversal, insertion (at beginning, end, before, after), and deletion.

Singly linked list (one direction), doubly linked list (forward and backward traversal), and circular linked list (last node points back to head).

Author

Embedded Systems trainer – IIES

Updated On: 01-06-26


10+ years of hands-on experience delivering practical training in Embedded Systems and it's design