Build the mental model
In a singly linked list, each node only points forward, so removing the last element still requires walking from the head to find its predecessor — O(n). A doubly linked list fixes this by giving every node a `prev` pointer alongside `next`, so from any node you can reach its neighbor in either direction in O(1), which means insertion and removal at both ends of the list are O(1). You rarely implement this by hand in Python because `collections.deque` already is one — it's built on a doubly linked structure internally, so `append`, `appendleft`, `pop`, and `popleft` are all O(1). That makes deque the theoretically correct choice — not just a convenience — whenever both ends of a sequence matter: queues, stacks, sliding windows, or a bounded recent-history buffer, all of which a plain Python list handles poorly at one end or the other.
Connect it to a real scenario
The Tutorial Platform's 'Recently Viewed Lessons' panel can be built directly on a deque — each newly opened lesson gets pushed to the front with appendleft(), and with maxlen set, the oldest entry automatically falls off the back, all in O(1).
Try the working example
from collections import deque
# deque is a doubly linked list under the hood, so both ends are O(1)
recent_lessons = deque(maxlen=3)
recent_lessons.append("intro") # add to right end - O(1)
recent_lessons.append("variables")
recent_lessons.appendleft("welcome") # add to left end - O(1), no shifting
print(list(recent_lessons))
recent_lessons.pop() # remove from right end - O(1)
recent_lessons.popleft() # remove from left end - O(1)
print(list(recent_lessons))
# Contrast: a plain list's pop(0) is O(n) because every remaining
# element must shift left by one to fill the gap.The first print shows ["welcome", "intro", "variables"]; after calling pop() and popleft(), the second print shows ["intro"].5-minute try-it
Rewrite the code using a plain Python list with insert(0, ...) and pop(0) instead — the result is the same, but reason about why it gets slower as the number of elements grows.
One important caution
Implementing a recent-history buffer with a plain list and `list.insert(0, item)` — every element has to shift, making it O(n) instead of O(1)
Assuming deque supports fast random access like an array — indexing into the middle (`d[5]`) is O(n) because of the underlying linked structure, unlike a list's O(1) indexing
Python Docs — collections.deque — Data Structures & Algorithms