Why Doubly Linked List Over Singly Linked List in DSA Python - Performance Analysis
We want to understand how operations like insertion, deletion, and traversal behave in doubly linked lists compared to singly linked lists.
Which list type performs faster for different tasks and why?
Analyze the time complexity of inserting and deleting nodes in a doubly linked list.
class Node:
def __init__(self, data):
self.data = data
self.prev = None
self.next = None
class DoublyLinkedList:
def __init__(self):
self.head = None
def insert_at_end(self, data):
new_node = Node(data)
if not self.head:
self.head = new_node
return
last = self.head
while last.next:
last = last.next
last.next = new_node
new_node.prev = last
def delete_node(self, node):
if not self.head or not node:
return
if node.prev:
node.prev.next = node.next
else:
self.head = node.next
if node.next:
node.next.prev = node.prev
This code inserts a node at the end and deletes a given node in a doubly linked list.
Look at the loops and pointer updates that repeat.
- Primary operation: Traversing the list to find the last node for insertion.
- How many times: The while loop runs once per node until the end, so up to n times for n nodes.
- Deletion: No loop needed if node is known; just pointer updates.
Traversal to the end grows linearly with the number of nodes.
| Input Size (n) | Approx. Operations |
|---|---|
| 10 | About 10 steps to reach end |
| 100 | About 100 steps to reach end |
| 1000 | About 1000 steps to reach end |
Pattern observation: The time to insert at end grows directly with list size.
Time Complexity: O(n) for insertion at end, O(1) for deletion if node is known.
This means insertion at the end takes longer as the list grows, but deleting a known node is quick because we can access neighbors directly.
[X] Wrong: "Doubly linked lists always take twice as long because they have two pointers."
[OK] Correct: Actually, doubly linked lists let us delete nodes faster without traversal, saving time in many cases despite extra pointers.
Understanding when to use doubly linked lists shows you know how to balance speed and memory in real problems.
"What if we kept a tail pointer in the doubly linked list? How would that change the insertion time complexity?"
Practice
Solution
Step 1: Understand pointer structure in doubly linked lists
Doubly linked lists have two pointers per node: one to the next node and one to the previous node.Step 2: Compare with singly linked lists
Singly linked lists only have a pointer to the next node, so they cannot move backwards easily.Final Answer:
Because doubly linked lists have pointers to both next and previous nodes, allowing backward traversal. -> Option AQuick Check:
Backward traversal needs previous pointers = A [OK]
- Thinking singly linked lists can traverse backwards easily
- Confusing memory usage with traversal ability
- Assuming doubly linked lists duplicate data
Solution
Step 1: Identify required pointers for doubly linked list node
A doubly linked list node must have pointers to both next and previous nodes.Step 2: Check each option for correct attributes
class Node: def __init__(self, data): self.data = data self.next = None self.prev = None correctly defines data, next, and prev attributes in __init__.Final Answer:
class Node:\n def __init__(self, data):\n self.data = data\n self.next = None\n self.prev = None -> Option AQuick Check:
Doubly linked list node needs next and prev = A [OK]
- Omitting the prev pointer
- Defining only prev without next
- Not initializing data attribute
class Node:
def __init__(self, data):
self.data = data
self.next = None
self.prev = None
class DoublyLinkedList:
def __init__(self):
self.head = None
def append(self, data):
new_node = Node(data)
if not self.head:
self.head = new_node
return
last = self.head
while last.next:
last = last.next
last.next = new_node
new_node.prev = last
def print_reverse(self):
current = self.head
while current.next:
current = current.next
result = []
while current:
result.append(current.data)
current = current.prev
print(result)
dll = DoublyLinkedList()
dll.append(1)
dll.append(2)
dll.append(3)
dll.print_reverse()Solution
Step 1: Append nodes 1, 2, 3 to doubly linked list
Nodes are added at the end, so list is 1 <-> 2 <-> 3.Step 2: Traverse to last node and print in reverse
print_reverse moves to last node (3), then collects data moving backward: 3, 2, 1.Final Answer:
[3, 2, 1] -> Option DQuick Check:
Reverse traversal collects nodes backward = B [OK]
- Confusing forward and backward traversal
- Printing in insertion order instead of reverse
- Not updating prev pointers correctly
def delete_last(self):
if not self.head:
return
current = self.head
while current.next:
current = current.next
current.prev.next = None
current.prev = NoneSolution
Step 1: Analyze deletion logic for last node
The code moves to the last node, then sets previous node's next to None to remove last node.Step 2: Check edge case for single-node list
If list has only one node, current.prev is None, so current.prev.next causes error.Final Answer:
It does not handle the case when the list has only one node. -> Option CQuick Check:
Single-node list needs special handling = C [OK]
- Ignoring empty or single-node list edge cases
- Assuming prev always exists
- Not updating head when list becomes empty
Solution
Step 1: Understand traversal needs for playlist
Moving forward and backward efficiently requires pointers in both directions.Step 2: Compare singly vs doubly linked lists
Doubly linked lists have next and prev pointers, enabling easy two-way traversal; singly linked lists only have next pointers.Step 3: Identify trade-off
Extra prev pointers in doubly linked lists use more memory compared to singly linked lists.Final Answer:
Doubly linked lists allow easy forward and backward moves; trade-off is extra memory for prev pointers. -> Option BQuick Check:
Two-way traversal needs prev pointers, costs extra memory = D [OK]
- Thinking singly linked lists support backward moves easily
- Ignoring memory cost of extra pointers
- Confusing speed with memory trade-offs
Ready to prove it?
Pass the test and this pattern moves unknown
