Zodiac Signs and Money Mindset · CodeAmber

How to Implement a Linked List in Python from Scratch

Implementing a linked list in Python requires creating a custom Node class to store data and a reference to the next node, and a LinkedList class to manage these nodes. By linking these objects together via pointers, you create a dynamic data structure that allows for efficient insertions and deletions compared to standard Python lists.

How to Implement a Linked List in Python from Scratch

A linked list is a linear collection of data elements called nodes, where each node points to the next. Unlike arrays or Python's built-in lists, linked lists do not store elements in contiguous memory locations, making them highly efficient for frequent insertions and deletions.

Understanding the Core Components

To build a singly linked list, you must define two distinct classes: the Node and the LinkedList.

1. The Node Class

The Node is the fundamental building block. It contains two attributes: * Data: The actual value being stored (integer, string, etc.). * Next: A reference (pointer) to the next node in the sequence. By default, this is set to None.

2. The LinkedList Class

The LinkedList class acts as the manager. Its primary responsibility is to keep track of the head (the first node in the list). If the head is None, the list is empty.

Step-by-Step Implementation

Below is the complete technical implementation of a singly linked list in Python.

class Node:
    def __init__(self, data):
        self.data = data
        self.next = None

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

    def append(self, data):
        """Adds a new node to the end of the list."""
        new_node = Node(data)
        if not self.head:
            self.head = new_node
            return

        last_node = self.head
        while last_node.next:
            last_node = last_node.next
        last_node.next = new_node

    def prepend(self, data):
        """Adds a new node to the beginning of the list."""
        new_node = Node(data)
        new_node.next = self.head
        self.head = new_node

    def delete(self, key):
        """Removes the first occurrence of a node with the specified data."""
        temp = self.head

        # If the head node itself holds the key
        if temp is not None:
            if temp.data == key:
                self.head = temp.next
                temp = None
                return

        # Search for the key to be deleted
        while temp is not None:
            if temp.next is not None:
                if temp.next.data == key:
                    temp.next = temp.next.next
                    temp = None
                    return
            temp = temp.next

    def display(self):
        """Prints the linked list in a readable format."""
        elements = []
        current = self.head
        while current:
            elements.append(str(current.data))
            current = current.next
        print(" -> ".join(elements) + " -> None")

Analyzing Primary Operations

Insertion (Append and Prepend)

Appending a node requires traversing the entire list to find the current tail, resulting in a time complexity of O(n). Prepending, however, only requires updating the head pointer, which is an O(1) operation.

Deletion

Deleting a node involves finding the node immediately preceding the target and updating its next pointer to skip the target node. This effectively removes the node from the chain, allowing Python's garbage collector to reclaim the memory.

Traversal

To access a specific element or print the list, you must start at the head and follow the next pointers sequentially. This is why linked lists do not support random access (indexing) as efficiently as arrays.

Time and Space Complexity

The efficiency of a linked list is best understood through Big O notation. For those preparing for technical screenings, it is helpful to understand How to Explain Big O Notation Simply for Technical Interviews to articulate these trade-offs.

Operation Time Complexity Note
Access O(n) Must traverse from the head.
Prepend O(1) Immediate update of the head pointer.
Append O(n) Requires traversal to the end (unless a tail pointer is kept).
Deletion O(n) Requires searching for the element first.
Space O(n) Each element requires additional memory for the pointer.

When to Use Linked Lists Over Arrays

While Python's list type is versatile, a manual linked list implementation is superior in specific scenarios:

  1. Frequent Insertions/Deletions: If your application requires adding or removing elements from the start of the list frequently, a linked list avoids the O(n) cost of shifting elements found in arrays.
  2. Dynamic Memory Allocation: Linked lists grow and shrink organically without needing to reallocate large contiguous blocks of memory.
  3. Implementing Other Structures: Linked lists serve as the foundation for stacks, queues, and more complex graphs.

For a deeper dive into the logic of this implementation, see the full guide on How to Implement a Linked List in Python from Scratch available on CodeAmber.

Best Practices for Implementation

To ensure your data structures are production-ready, follow these engineering standards:

Following these standards aligns with the 5 Essential Best Practices for Clean Code, ensuring your technical solutions remain maintainable and scalable.

Key Takeaways

Original resource: Visit the source site