What you'll learn
Quick Answer
A linked list stores each value in a node holding a reference to the next node. Insertion at the front is instant, but reaching item n takes n steps. In Python a built-in list is usually better in practice — linked lists matter for interviews and for understanding pointers.
It is just nodes pointing at nodes
class Node:
def __init__(self, data):
self.data = data
self.next = None
That is the entire idea. Each node holds a value and a reference to the next node. The last node's next is None, which is how you know you have reached the end.
Unlike a Python list, the nodes are not stored side by side in memory. They can be anywhere; the chain is held together by the references. That single difference explains every performance property that follows.
Building the list
class LinkedList:
def __init__(self):
self.head = None
def append(self, data):
n = Node(data)
if not self.head:
self.head = n
return
cur = self.head
while cur.next:
cur = cur.next
cur.next = n
def to_list(self):
out, cur = [], self.head
while cur:
out.append(cur.data)
cur = cur.next
return out
ll = LinkedList()
for v in [10, 20, 30]:
ll.append(v)
print(ll.to_list()) # [10, 20, 30]
Note what append costs. To add at the end it walks the entire list first, which is O(n). Adding at the front would be O(1) — create a node, point it at the current head, done. That asymmetry is the whole character of a linked list.
The if not self.head branch is the empty-list case, and forgetting it is the single most common bug when writing this from memory in an interview.
Reversal: the problem worth knowing cold
This appears in interviews constantly. The technique is three pointers moving in step:
def reverse(self):
prev, cur = None, self.head
while cur:
nxt = cur.next # remember where we were going
cur.next = prev # flip this link backwards
prev, cur = cur, nxt # step both forward
self.head = prev
ll.reverse()
print(ll.to_list()) # [30, 20, 10]
The line that must come first is nxt = cur.next. The moment you write cur.next = prev, the forward link is gone — if you have not saved it, the rest of the list is unreachable and lost. Interviewers watch for exactly that ordering.
Then self.head = prev, not cur. When the loop ends cur is None, and prev is sitting on the last real node, which is the new head. Getting this wrong produces an empty list, which is the second most common mistake.
Linked list versus Python's built-in list
Honestly compared:
- Access by index — Python list O(1), linked list O(n). You must walk from the head.
- Insert at front — Python list O(n) because everything shifts, linked list O(1).
- Insert at end — Python list O(1) amortised, linked list O(n) unless you keep a tail pointer.
- Memory — linked list uses more, since every value carries a reference alongside it.
- Cache behaviour — contiguous arrays are dramatically faster in practice, which is why real code rarely uses linked lists.
For everyday Python, use the built-in list. If you need fast insertion at both ends, use collections.deque, which is implemented for exactly that. Writing your own linked list in production Python is almost always the wrong call — and saying so in an interview, after implementing it correctly, is a good answer.
The related problems that keep appearing
Once reversal makes sense, these become approachable, and they share techniques:
- Find the middle — two pointers, one moving twice as fast. When the fast one reaches the end, the slow one is at the middle.
- Detect a cycle — the same two-speed idea. If the pointers ever meet, there is a loop.
- Remove the nth node from the end — two pointers separated by n.
- Merge two sorted lists — the merge step from merge sort.
Almost all of them are two pointers moving at different speeds or distances. Learn that pattern and this whole category becomes one idea rather than four. See the time complexity cheat sheet for how to talk about their cost.
