Skip to main content
HomeTutorialsPython

Python Linked Lists: Tutorial With Examples

Learn everything you need to know about linked lists: when to use them, their types, and implementation in Python.
Feb 2024  · 9 min read

A linked list is a data structure that plays a crucial role in data organization and management. It contains a series of nodes that are stored at random locations in memory, allowing for efficient memory management. Each node in a linked list contains two main components: the data part and a reference to the next node in the sequence.

If this concept sounds complex at first glance, don’t fret!

We’ll break it down to its fundamentals to explain what linked lists are, why we use them, and the unique advantages they offer.

Why Linked Lists?

Linked lists were created to overcome various drawbacks associated with storing data in regular lists and arrays, as outlined below:

Ease of insertion and deletion

In lists, inserting or deleting an element at any position other than the end requires shifting all the subsequent items to a different position. This process has a time complexity of O(n) and can significantly degrade performance, especially as the list size grows. If you aren’t already familiar with how lists work or their implementation, you can read our tutorial on Python lists.

Linked lists, however, operate differently. They store elements in various, non-contiguous memory locations and connect them through pointers to subsequent nodes. This structure allows linked lists to add or remove elements at any position by simply modifying the links to include a new element or bypass the deleted one.

Once the position of the element is identified and there is direct access to the point of insertion or deletion, adding or removing nodes can be achieved in O(1) time.

Dynamic size

Python lists are dynamic arrays, which means that they provide the flexibility to modify size.

However, this process involves a series of complex operations, including reallocating the array to a new, larger memory block. Such reallocation is inefficient since elements are copied over to a new block, potentially allocating more space than is immediately necessary.

In contrast, linked lists can grow and shrink dynamically without the need for reallocation or resizing. This makes them a preferable option for tasks that require high flexibility.

Memory efficiency

Lists allocate memory for all of its elements in a contiguous block. If a list needs to grow beyond its initial size, it must allocate a new, larger block of contiguous memory and then copy all existing elements to this new block. This process is time-consuming and inefficient, especially for large lists. On the other hand, if the initial size of the list is overestimated, the unused memory is wasted.

In contrast, linked lists allocate memory for each element separately. This structure leads to better memory utilization since memory for new elements can be allocated as they are added.

When Should You Use Linked Lists?

While linked lists provide certain benefits over regular lists and arrays, such as dynamic size and memory efficiency, they also have their limitations. Since pointers for each element must be stored to reference the next node, the memory usage per element is higher when using linked lists. Also, this data structure does not allow direct access to data. Accessing an element requires sequential traversal from the beginning of the list, resulting in O(n) search time complexity.

The choice between using a linked list or an array depends on the specific needs of the application. Linked lists are most useful when:

  • You need to frequently insert and delete many elements
  • The data size is unpredictable or likely to change frequently
  • Direct access to elements is not a requirement
  • The dataset contains large elements or structures

Types of linked lists

There are three types of linked lists, each offering unique advantages for different scenarios. These types are:

Singly-linked lists

Image of a singly linked list

Singly-linked list

A singly-linked list is the simplest type of linked list, where each node contains some data and a reference to the next node in the sequence. They can only be traversed in a single direction - from the head (the first node) to the tail (the last node).

Each node in a singly-linked list typically consists of two parts:

  • Data: The actual information stored in the node.
  • Next Pointer: A reference to the next node. The last node’s next pointer is usually set to null.

Since these data structures can only be traversed in a single direction, accessing a specific element by value or index requires starting from the head and sequentially moving through the nodes until the desired node is found. This operation has a time complexity of O(n), making it less efficient for large lists.

Inserting and deleting a node at the beginning of a singly-linked list is highly efficient with a time complexity of O(1). However, insertion and deletion in the middle or at the end requires traversing the list until that point, leading to an O(n) time complexity.

The design of singly-linked lists makes them a useful data structure when performing operations that occur at the beginning of the list.

Doubly-linked lists

Image of a doubly linked list

Doubly-linked list

One disadvantage of singly-linked lists is that we can only traverse them in a single direction and cannot iterate back to the previous node if required. This constraint limits our ability to perform operations that require bidirectional navigation.

Doubly-linked lists solve this problem by incorporating an additional pointer within each node, ensuring that the list can be traversed in both directions. Each node in a doubly linked list contains three elements: the data, a pointer to the next node, and a pointer to the previous node.

Circular linked lists

Image of a circular linked list

Circular linked list

Circular linked lists are a specialized form of linked list where the last node points back to the first node, creating a circular structure. This means that, unlike the singly and doubly linked lists we’ve seen so far, the circular linked list does not end; instead, it loops around.

The cyclical nature of circular linked lists makes them ideal for scenarios that need to be looped through continuously, such as board games that loop back from the last player to the first, or in computing algorithms such as round-robin scheduling.

How to Create a Linked List in Python

Now that we understand what linked lists are, why we use them, and their variations, let’s proceed to implement these data structures in Python. The notebook for this tutorial has also been published in DataCamp Workspaces. If you encounter any issues running the code on your own, you can access the notebook there and create a copy of it.

Initializing a node

As we learned previously, a node is an element in the linked list that stores data and a reference to the next node in the sequence. Here is how you can define a node in Python:

class Node:
    def __init__(self, data):
        self.data = data  # Assigns the given data to the node
        self.next = None  # Initialize the next attribute to null

The above code initializes a node by performing two primary actions: The “data” attribute of the node is assigned a value representing the actual information the node is intended to contain. The “next” attribute represents the address of the next node. This is currently set to None, signifying that it does not link to any other node in the list. As we continue to add new nodes to the linked list, this attribute will be updated to point to the subsequent node.

Creating a linked list class

Next, we need to create the linked list class. This will encapsulate all the operations for managing the nodes, such as insertion and removal. We will start by initializing the linked list:

class LinkedList:
    def __init__(self):
        self.head = None  # Initialize head as None

By setting self.head to None, we are stating that the linked list is initially empty and that there are no nodes in the list to point to. We will now proceed to populate the list by inserting new nodes.

Inserting a new node at the beginning of a linked list

Within the LinkedList class, we are going to add a method to create a new node and place it at the start of the list:

    def insertAtBeginning(self, new_data):
        new_node = Node(new_data)  # Create a new node 
        new_node.next = self.head  # Next for new node becomes the   current head
        self.head = new_node  # Head now points to the new node

Every time you call the above method, a new node is created with your specified data. The next pointer of this new node is set to the current head of the list, which will place this node in front of the existing nodes. Finally, the newly created node is made the head of the list.

We are now going to populate this linked list with a series of words to get a better understanding of how the insertion operation works. To accomplish this, let’s first create a method designed to traverse and print the list’s contents:

    def printList(self):
        temp = self.head # Start from the head of the list
        while temp:
            print(temp.data,end=' ') # Print the data in the current node
            temp = temp.next # Move to the next node
        print()  # Ensures the output is followed by a new line

The above method will print the contents of our linked list. Let’s now proceed to use the methods we’ve defined to populate our list with a series of words: “the quick brown fox.”

if __name__ == '__main__':
    # Create a new LinkedList instance
    llist = LinkedList()

    # Insert each letter at the beginning using the method we created
    llist.insertAtBeginning('fox') 
    llist.insertAtBeginning('brown') 
    llist.insertAtBeginning('quick')  
    llist.insertAtBeginning('the')  

    # Now 'the' is the head of the list, followed by 'quick', then 'brown' and 'fox'

    # Print the list
    llist.printList()

The above lines of code should render the following output:

"the quick brown fox"

Inserting a new node at the end of a linked list

We will now create a method called insertAtEnd within the LinkedList class, to create a new node at the end of the list. If the list is empty, the new node will become the head of the list. Otherwise, it will be appended to the current last node in the list. Let’s see how this works in practice:

def insertAtEnd(self, new_data):
        new_node = Node(new_data)  # Create a new node
        if self.head is None:
            self.head = new_node  # If the list is empty, make the new node the head
            return
        last = self.head 
        while last.next:  # Otherwise, traverse the list to find the last node
            last = last.next
        last.next = new_node  # Make the new node the next node of the last node

The above method starts by creating a new node. It then checks if the list is empty, and if so, the new node is assigned as the head of that list. Otherwise, it traverses the list to find the last node and sets the pointer of this node to the new node.

We now need to include this method in our LinkedList class and use it to add a word to the end of our list. To accomplish this, amend your main function to look like this:

if __name__ == '__main__':
    llist = LinkedList()

    # Insert words at the beginning
    llist.insertAtBeginning('fox')
    llist.insertAtBeginning('brown')
    llist.insertAtBeginning('quick')
    llist.insertAtBeginning('the')

    # Insert a word at the end
    llist.insertAtEnd('jumps')

    # Print the list
    llist.printList()

Notice that we have simply called the insertAtEnd method to print the word “jumps” at the end of the list. The above code should render the following output:

"the quick brown fox jumps"

Deleting a node from the beginning of a linked list

Deleting the first node of a linked list is easy since it simply involves pointing the head of this list to the second node. This way, the first node will no longer be a part of the list. To accomplish this, include the following method in the LinkedList class:

def deleteFromBeginning(self):
    if self.head is None:
        return "The list is empty" # If the list is empty, return this string
    self.head = self.head.next  # Otherwise, remove the head by making the next node the new head

Deleting a node from the end of a linked list

To delete the last node of a linked list, we must traverse the list to find the second-last node and change its next pointer to None. This way, the last node will no longer be a part of the list. Copy and paste the following method into your LinkedList class to accomplish this:

def deleteFromEnd(self):
    if self.head is None:
        return "The list is empty" 
    if self.head.next is None:
        self.head = None  # If there's only one node, remove the head by making it None
        return
    temp = self.head
    while temp.next.next:  # Otherwise, go to the second-last node
        temp = temp.next
    temp.next = None  # Remove the last node by setting the next pointer of the second-last node to None

The above method first checks if the linked list is empty, returning a message to the user if it is. Otherwise, if the list contains a single node, that node is removed. For lists with multiple nodes, the method locates the second-last node, and its next node reference is updated to None.

Let’s now update the main function to delete elements from the start and end of the linked list:

if __name__ == '__main__':
    llist = LinkedList()

    # Insert words at the beginning
    llist.insertAtBeginning('fox')
    llist.insertAtBeginning('brown')
    llist.insertAtBeginning('quick')
    llist.insertAtBeginning('the')

    # Insert a word at the end
    llist.insertAtEnd('jumps')

   # Print the list before deletion
    print("List before deletion:")
    llist.printList()

    # Deleting nodes from the beginning and end
    llist.deleteFromBeginning()
    llist.deleteFromEnd()

    # Print the list after deletion
    print("List after deletion:")
    llist.printList()

The above code will print the list before and after deletion, showcasing how the insert and delete operations work in linked lists. You should see the following output after running this code:

List before deletion:
the quick brown fox jumps 
List after deletion:
quick brown fox

Searching the linked list for a specific value

The final operation we will learn in this chapter is the retrieval of a specific value in the linked list. To accomplish this, the method should start at the head of the list and iterate through each node, checking if the node’s data matches the search value. Here is a practical implementation of this operation:

def search(self, value):
    current = self.head  # Start with the head of the list
    position = 0  # Counter to keep track of the position
    while current: # Traverse the list
        if current.data == value: # Compare the list's data to the search value
            return f"Value '{value}' found at position {position}" # Print the value if a match is found
        current = current.next
        position += 1
    return f"Value '{value}' not found in the list" 

To find specific values in the linked list we’ve created, update your main function to include the search method we’ve just created:

if __name__ == '__main__':
    llist = LinkedList()

    # Insert words at the beginning
    llist.insertAtBeginning('fox')
    llist.insertAtBeginning('brown')
    llist.insertAtBeginning('quick')
    llist.insertAtBeginning('the')

    # Insert a word at the end
    llist.insertAtEnd('jumps')

   # Print the list before deletion
    print("List before deletion:")
    llist.printList()

    # Deleting nodes from beginning and end
    llist.deleteFromBeginning()
    llist.deleteFromEnd()

    # Print the list after deletion
    print("List after deletion:")
    llist.printList()
    
        # Search for 'quick' and 'lazy' in the list
    print(llist.search('quick'))  # Expected to find
    print(llist.search('lazy'))   # Expected not to find

The above code will render the following output:

List before deletion:
the quick brown fox jumps 
List after deletion:
quick brown fox 
Value 'quick' found at position 0
Value 'lazy' not found in the list

The word “quick” has been successfully located within the linked list since it exists in the first position of the list. However, the word “lazy” isn’t part of the list, which is why it hasn’t been found.

Final Thoughts

If you’ve made it this far, congratulations! You now have a solid understanding of the basic principles of linked lists, including their structure, types, how to add and remove elements, and how to traverse them.

But the journey doesn’t stop here. Linked lists are just the beginning of the world of data structures and algorithms. Here are some potential next steps for you to deepen your understanding of the subject:

Create your own project

Dive into the practical applications of linked lists by integrating them into a coding or data science project. Linked lists are used to develop file systems, construct hash tables, and even create GPS navigation systems and board games. To get started with your own projects, check out our free guided data science projects that teach you how to solve real-world problems in Python, R, and SQL.

Learn about data structures and algorithms

Learning other data structures, such as trees, stacks, and queues, is a natural progression from understanding linked lists. These structures build upon the principles of linked lists, helping you solve a wider range of computational problems efficiently. Trees and binary search trees, for instance, extend the concept of linked lists into a hierarchical form, allowing each node to connect to multiple elements in the data structure.

If these concepts sound unfamiliar to you, don’t fret! Datacamp has an entire course on data structures and algorithms in Python that will take you through these concepts in greater detail. You will first learn about data structures such as stacks, trees, hash tables, queues, and graphs. As you progress through the course, you will gain an understanding of searching and sorting algorithms, which will help you become a more efficient programmer and problem-solver.

Exploring advanced linked list concepts

We have implemented singly-linked lists in this tutorial, covering operations like insertion, deletion, and traversal.

You can take this knowledge a step further by learning the implementation of doubly and circular linked lists. Skip lists are another extension of linked lists that allow for faster search operations by facilitating quicker access to elements.

Learning about these advanced data structures will take your technical skills to the next level and dramatically improve your programming capabilities, preparing you for more complex challenges in fields like data science, software development, and machine learning engineering.

If you’d like a more beginner-friendly introduction to programming prior to tackling these advanced topics, explore our Python Programmer career track. It offers a series of courses which will teach you the fundamentals of the language.


Photo of Natassha Selvaraj
Author
Natassha Selvaraj

Natassha is a data consultant who works at the intersection of data science and marketing. She believes that data, when used wisely, can inspire tremendous growth for individuals and organizations. As a self-taught data professional, Natassha loves writing articles that help other data science aspirants break into the industry. Her articles on her personal blog, as well as external publications garner an average of 200K monthly views.

Topics

Keep Learning Python!

Track

Python Fundamentals

15hrs hr
Grow your programmer skills. Discover how to manipulate dictionaries and DataFrames, visualize real-world data, and write your own Python functions.
See DetailsRight Arrow
Start Course
Certification available

Course

Intermediate Python

4 hr
1.1M
Level up your data science skills by creating visualizations using Matplotlib and manipulating DataFrames with pandas.
See MoreRight Arrow
Related

Becoming Remarkable with Guy Kawasaki, Author and Chief Evangelist at Canva

Richie and Guy explore the concept of being remarkable, growth, grit and grace, the importance of experiential learning, imposter syndrome, finding your passion, how to network and find remarkable people, measuring success through benevolent impact and much more. 
Richie Cotton's photo

Richie Cotton

55 min

Mastering the Pandas .explode() Method: A Comprehensive Guide

Learn all you need to know about the pandas .explode() method, covering single and multiple columns, handling nested data, and common pitfalls with practical Python code examples.
Adel Nehme's photo

Adel Nehme

5 min

Python NaN: 4 Ways to Check for Missing Values in Python

Explore 4 ways to detect NaN values in Python, using NumPy and Pandas. Learn key differences between NaN and None to clean and analyze data efficiently.
Adel Nehme's photo

Adel Nehme

5 min

Seaborn Heatmaps: A Guide to Data Visualization

Learn how to create eye-catching Seaborn heatmaps
Joleen Bothma's photo

Joleen Bothma

9 min

Test-Driven Development in Python: A Beginner's Guide

Dive into test-driven development (TDD) with our comprehensive Python tutorial. Learn how to write robust tests before coding with practical examples.
Amina Edmunds's photo

Amina Edmunds

7 min

Exponents in Python: A Comprehensive Guide for Beginners

Master exponents in Python using various methods, from built-in functions to powerful libraries like NumPy, and leverage them in real-world scenarios to gain a deeper understanding.
Satyam Tripathi's photo

Satyam Tripathi

9 min

See MoreSee More