Creating a Singly Linked List from Scratch in DSA Python - Complexity Walkthrough
When we create a singly linked list from scratch, we add nodes one by one. Understanding how the time needed grows as we add more nodes helps us write better code.
We want to know: How does the time to build the list change as the list gets longer?
Analyze the time complexity of the following code snippet.
class Node:
def __init__(self, data):
self.data = data
self.next = None
class SinglyLinkedList:
def __init__(self):
self.head = None
def append(self, data):
new_node = Node(data)
if not self.head:
self.head = new_node
return
current = self.head
while current.next:
current = current.next
current.next = new_node
This code creates a singly linked list and adds new nodes at the end one by one.
Identify the loops, recursion, array traversals that repeat.
- Primary operation: The
whileloop inside theappendmethod that moves through the list to find the last node. - How many times: For each new node added, the loop runs once for each existing node in the list until it reaches the end.
As the list grows, adding a new node takes longer because we must walk through all existing nodes to find the end.
| Input Size (n) | Approx. Operations |
|---|---|
| 10 | About 1 + 2 + ... + 10 = 55 steps |
| 100 | About 1 + 2 + ... + 100 = 5,050 steps |
| 1000 | About 1 + 2 + ... + 1000 = 500,500 steps |
Pattern observation: The total steps grow roughly with the square of the number of nodes, because each append walks through the list.
Time Complexity: O(n2)
This means that adding n nodes one by one like this takes time proportional to n squared, because each new node requires walking through the whole list so far.
[X] Wrong: "Appending a node always takes the same time, so building the whole list is O(n)."
[OK] Correct: Without keeping track of the last node, each append must walk through the entire list, making later appends slower and total time grow faster than just n.
Knowing how linked list operations scale helps you explain trade-offs clearly. This skill shows you understand how data structures work under the hood, which is valuable in real coding tasks.
"What if we kept a pointer to the last node to avoid walking the list each time? How would the time complexity change?"
Practice
Singly Linked List in data structures?Solution
Step 1: Understand the structure of a singly linked list
A singly linked list stores elements in nodes where each node points to the next node, forming a chain.Step 2: Compare with other data structures
Unlike arrays or trees, singly linked lists do not store keys or have parent-child relationships; they only link sequentially.Final Answer:
To store elements in a sequence where each element points to the next -> Option BQuick Check:
Singly Linked List = sequence with next pointers [OK]
- Confusing linked lists with arrays or hash maps
- Thinking nodes have backward pointers (that's doubly linked list)
- Assuming linked lists store key-value pairs
Solution
Step 1: Identify the correct attributes for a singly linked list node
A singly linked list node stores data and a pointer to the next node only, so it needsdataandnext.Step 2: Check the constructor parameters and attributes
class Node: def __init__(self, data): self.data = data self.next = None correctly initializesdataand setsnextto None by default. Other options includeprevwhich is for doubly linked lists or incorrect parameters.Final Answer:
class Node: def __init__(self, data): self.data = data self.next = None -> Option CQuick Check:
Node has data and next only [OK]
- Adding a 'prev' pointer by mistake
- Not initializing 'next' to None
- Using extra parameters unnecessarily
class Node:
def __init__(self, data):
self.data = data
self.next = None
class LinkedList:
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
def print_list(self):
current = self.head
while current:
print(current.data, end=' -> ')
current = current.next
print('None')
ll = LinkedList()
ll.append(10)
ll.append(20)
ll.append(30)
ll.print_list()Solution
Step 1: Trace the append method calls
Appending 10 sets head to node with data 10. Then 20 and 30 are added at the end by traversing the list.Step 2: Trace the print_list method
Prints nodes in order: 10, then 20, then 30, each followed by ' -> ', ending with 'None'.Final Answer:
10 -> 20 -> 30 -> None -> Option DQuick Check:
Append adds at end, print shows order [OK]
- Assuming append adds at front
- Printing without 'None' at end
- Confusing order of nodes
class Node:
def __init__(self, data):
self.data = data
self.next = None
class LinkedList:
def __init__(self):
self.head = None
def append(self, data):
new_node = Node(data)
if self.head is None:
self.head = new_node
else:
last = self.head
while last.next:
last = last.next
last = new_node
ll = LinkedList()
ll.append(1)
ll.append(2)
ll.append(3)
current = ll.head
while current:
print(current.data, end=' -> ')
current = current.next
print('None')Solution
Step 1: Analyze the append method's else block
The code finds the last node correctly but then does 'last = new_node' which only changes the local variable, not the last node's next pointer.Step 2: Identify the fix
To link the new node, it should be 'last.next = new_node' to update the last node's next pointer.Final Answer:
The line 'last = new_node' does not link the new node to the list -> Option AQuick Check:
Assign to last.next to link nodes [OK]
- Assigning new_node to last instead of last.next
- Not updating the head when list is empty
- Confusing variable assignment with pointer update
Solution
Step 1: Understand insert at beginning logic
To insert at the beginning, new node's next should point to current head, then head updated to new node.Step 2: Check each option's insert_at_beginning method
class Node: def __init__(self, data): self.data = data self.next = None class LinkedList: def __init__(self): self.head = None def insert_at_beginning(self, data): new_node = Node(data) new_node.next = self.head self.head = new_node def print_list(self): current = self.head while current: print(current.data, end=' -> ') current = current.next print('None') correctly sets new_node.next = self.head and updates head. class Node: def __init__(self, data): self.data = data self.prev = None self.next = None class LinkedList: def __init__(self): self.head = None def insert_at_beginning(self, data): new_node = Node(data) new_node.prev = self.head self.head = new_node def print_list(self): current = self.head while current: print(current.data, end=' -> ') current = current.next print('None') wrongly uses prev pointer. class Node: def __init__(self, data): self.data = data self.next = None class LinkedList: def __init__(self): self.head = None def insert_at_beginning(self, data): new_node = Node(data) self.head = new_node def print_list(self): current = self.head while current: print(current.data, end=' -> ') current = current.next print('None') misses linking new_node.next. class Node: def __init__(self, data): self.data = data self.next = None class LinkedList: def __init__(self): self.head = None def insert_at_beginning(self, data): new_node = Node(data) new_node.next = None self.head = new_node def print_list(self): current = self.head while current: print(current.data, end=' -> ') current = current.next print('None') sets new_node.next to None, losing old list.Final Answer:
Option A code snippet correctly inserts at beginning and prints list -> Option AQuick Check:
New node points to old head, then head updated [OK]
- Not linking new node to old head
- Using prev pointer in singly linked list
- Setting new_node.next to None incorrectly
Ready to prove it?
Pass the test and this pattern moves unknown
