Quick wins for a faster PC:
Clear out junk files and repair common Windows errorsFree Scan →Fix the driver behind crashes, sound loss and screen glitchesFind Drivers →Implement a singly linked list in Python with a Node object that stores a value and a reference to the next node, plus a list object that tracks its head. Keep a tail when you need constant-time appends and a size counter when constant-time length checks matter. Traversal, searching and index lookup remain O(n), so Python’s built-in list or collections.deque is usually a better production choice unless you specifically need node links.
The data model: nodes and links
A singly linked list is a chain of nodes. Each node contains a payload and one reference, conventionally called next. The final node points to None. The container keeps the first node in head; retaining tail avoids walking the chain for every append.
class Node:
def __init__(self, value, next_node=None):
self.value = value
self.next = next_node
class LinkedList:
def __init__(self):
self.head = None
self.tail = None
self.size = 0
An empty list has head is None and tail is None. For a non-empty list, head and tail refer to nodes in the same chain, and tail.next must be None. These are the core invariants your methods must preserve.
A complete singly linked-list implementation
The following implementation supports append, prepend, search, indexed lookup, deletion by value, deletion after a known predecessor, iteration and length. It uses a documented policy for empty operations: lookup and deletion return None or False rather than raising an accidental attribute error.
#1 Best Overall
class Node:
def __init__(self, value, next_node=None):
self.value = value
self.next = next_node
def __repr__(self):
return f"Node({self.value!r})"
class LinkedList:
def __init__(self):
self.head = None
self.tail = None
self.size = 0
def __len__(self):
return self.size
def append(self, value):
"""Add value at the end in O(1) time."""
node = Node(value)
if self.head is None:
self.head = self.tail = node
else:
self.tail.next = node
self.tail = node
self.size += 1
def prepend(self, value):
"""Add value at the beginning in O(1) time."""
node = Node(value, self.head)
self.head = node
if self.tail is None:
self.tail = node
self.size += 1
def find(self, value):
"""Return the first matching Node, or None."""
current = self.head
while current is not None:
if current.value == value:
return current
current = current.next
return None
def get(self, index):
"""Return the value at index; raise IndexError if absent."""
if index < 0:
raise IndexError("negative indexes are not supported")
current = self.head
for _ in range(index):
if current is None:
raise IndexError("linked-list index out of range")
current = current.next
if current is None:
raise IndexError("linked-list index out of range")
return current.value
def remove_first(self, value):
"""Remove the first matching value and return True if removed."""
previous = None
current = self.head
while current is not None:
if current.value == value:
if previous is None:
self.head = current.next
else:
previous.next = current.next
if current is self.tail:
self.tail = previous
self.size -= 1
if self.size == 0:
self.head = self.tail = None
return True
previous, current = current, current.next
return False
def remove_after(self, predecessor):
"""Remove predecessor.next; return the removed value or None."""
if predecessor is None or predecessor.next is None:
return None
removed = predecessor.next
predecessor.next = removed.next
if removed is self.tail:
self.tail = predecessor
self.size -= 1
return removed.value
def pop_front(self):
"""Remove and return the first value, or None when empty."""
if self.head is None:
return None
value = self.head.value
self.head = self.head.next
self.size -= 1
if self.head is None:
self.tail = None
return value
def __iter__(self):
current = self.head
while current is not None:
yield current.value
current = current.next
def __repr__(self):
return "LinkedList([" + ", ".join(repr(x) for x in self) + "])"
if __name__ == "__main__":
numbers = LinkedList()
numbers.append(2)
numbers.prepend(1)
numbers.append(3)
print(numbers) # LinkedList([1, 2, 3])
print(numbers.get(1)) # 2
print(numbers.find(3)) # Node(3)
print(numbers.remove_first(2)) # True
print(list(numbers)) # [1, 3]
Appending and prepending
append links a new node from the old tail and then moves tail. The first append initializes both endpoints. prepend points the new node at the old head and moves head; if the list was empty, it also initializes tail.
Traversal and search
Starting at head, follow current.next until the reference is None. find returns the node, which is useful when a later operation already has a node reference. Returning the node rather than only a Boolean avoids a second traversal.
Deletion details
To remove a node from a singly linked list, the predecessor must skip it by assigning predecessor.next = removed.next. Removing the head is a special case because there is no predecessor. Removing the tail requires moving tail to the predecessor. If the final node is removed, both endpoints must become None.
Complexity: linked list versus Python containers
The table assumes a singly linked list that stores both head and tail. “Remove after predecessor” is constant time only when that predecessor node is already known; finding it is linear.
Do these 3 things before closing this tab:
1Clear out junk files and repair common Windows errors2Fix the driver behind crashes, sound loss and screen glitches3Repair Windows errors before they cause bigger problems| Operation/design | Singly linked list (head and tail) | Python list |
collections.deque |
|---|---|---|---|
| Indexing | O(n) traversal | O(1) | O(1) at ends; slower in the middle |
| Prepend | O(1) | O(n), because elements shift | Approximately O(1) with appendleft |
| Append | O(1) with tail; O(n) without it |
Amortized O(1) | Approximately O(1) |
| Search | O(n) | O(n) | O(n) |
| Remove after known predecessor | O(1) | Usually O(n) due to shifting | Endpoint operations are approximately O(1) |
Python’s lists are variable-length arrays backed by a contiguous array of references, so indexing does not depend on list size. The Python tutorial recommends collections.deque for queues; its documentation describes approximately O(1) performance for appends and pops at either end, while indexing in the middle is slower.
Choosing the right structure
Use a custom linked list when links are the lesson or the requirement
- You are learning pointers-by-reference, invariants and node-based algorithms.
- An algorithm already holds node references and must splice nodes without shifting an array.
- You need a specialized structure whose semantics are not supplied by standard containers.
Use Python list for indexed, compact sequences
Choose a list for random access, compact storage and cache-friendly iteration. A custom linked list creates a separate Python object for every node and adds a reference hop for every element, so it can use more memory and iterate less efficiently than a contiguous list.
Use deque for queues, stacks and double-ended workloads
For production code that repeatedly adds or removes at both ends, import deque and use append, appendleft, pop and popleft. It supplies the operation profile most application queues need without maintaining your own pointer invariants.
Testing the edge cases that break linked lists
Test transitions, not just a long happy-path sequence:
- Start empty and verify
head,tailandlen(list). - Append one value; confirm
head is tailandtail.next is None. - Prepend to a one-node list and append after prepending.
- Remove the head, then remove the tail, then remove the only remaining node.
- Search for a missing value and for duplicate values; confirm only the first duplicate is removed.
- Call
getwith zero, the last valid index and an out-of-range index. - Repeat append/remove operations and check that
sizeequals the number produced bylist(linked_list).
def check_invariants(linked):
values = list(linked)
assert len(linked) == len(values)
assert (linked.head is None) == (len(values) == 0)
assert (linked.tail is None) == (len(values) == 0)
if linked.tail is not None:
assert linked.tail.next is None
assert linked.find(values[-1]) is not None
items = LinkedList()
check_invariants(items)
items.append("a")
check_invariants(items)
items.prepend("b")
items.append("a")
assert items.remove_first("a")
assert list(items) == ["b", "a"]
assert items.remove_first("missing") is False
check_invariants(items)
Troubleshooting common implementation failures
Append raises an attribute error on an empty list
Cause: the method writes to self.tail.next before initializing tail. Fix the empty branch first, assigning self.head = self.tail = node.
The tail still points to a deleted node
Cause: deletion updates the predecessor but not the endpoint. When the removed node is tail, assign tail = predecessor; when the list becomes empty, clear both endpoints.
Rank #3
Iteration never ends
Cause: a link points backward or to an earlier node, creating a cycle. Assign each next exactly once during ordinary insertion and assert tail.next is None. If cycles are valid for your algorithm, a normal terminating iterator needs a separate visited-node policy.
The reported length disagrees with iteration
Cause: an early return skipped size += 1 or size -= 1, or a caller changed links directly. Keep structural changes inside methods and test the size after every mutation.
Recommended Free Tools
Indexing is unexpectedly slow
Cause: every lookup walks from head. A linked list cannot provide array-style random access without an additional index structure; use a Python list when frequent indexing is central.
Performance, memory and reliability considerations
Keeping tail changes append from a full traversal to O(1), but it does not improve search or indexing. Keeping size makes len O(1) at the cost of maintaining another invariant. If you do not need those operations, a smaller implementation can omit the fields, accepting the corresponding trade-offs.
Node objects carry Python-object and reference overhead. For large homogeneous data, a built-in list is generally more compact; for endpoint queues, deque avoids custom pointer maintenance. A linked list is reliable only when all mutation paths handle the empty, one-node, head and tail cases consistently.
Rank #4
Or skip the browser setup
If you need screenshots of documentation, demos or a rendered linked-list visual rather than a browser automation script, ScreenshotNeo returns an image or PDF from one GET request. Its cleanup steps accept cookie and consent banners like a visitor, then remove more than 60 known consent platforms, newsletter popups and chat widgets; each step can be disabled. Only clean shots are billed: bot checks or CAPTCHAs, blank pages, timeouts, failed loads and cache hits cost nothing, and response headers report X-Page-Verdict and X-Billed.
The Tool Desk
Outbyte Driver Updater FREEScan for outdated or missing drivers - takes under a minuteDriver Scan →Outbyte PC Repair FREERepair Windows errors before they cause bigger problemsFix Now →Example using the documented API (see the ScreenshotNeo documentation):
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
It also provides an MCP server with take_screenshot, get_page_info and capture_pdf for Claude, Cursor and other MCP clients. The free plan includes 1,000 shots per month with no card; paid plans start at $5 for 3,000 shots. Create a free ScreenshotNeo account.
FAQ
Can a linked list support duplicate values?
Yes. Values are compared with ==; the sample’s removal method intentionally removes only the first matching node.
Why does the sample reject negative indexes?
Negative-index behavior is a policy choice. The implementation raises IndexError so it does not silently diverge from the documented traversal contract; you can add explicit translation from the end if your API requires it.
Is a doubly linked list faster?
It can move in both directions and delete a node when its neighboring references are available, but every node needs an additional prev reference and every mutation must maintain more links. Use it only when backward traversal or that deletion pattern justifies the extra overhead.
Best Value
Frequently Asked Questions
Can a linked list support duplicate values?
Yes. Values are compared with ==; the sample’s removal method removes only the first matching node.
Why does the sample reject negative indexes?
Negative-index behavior is a policy choice. This implementation raises IndexError; add explicit translation from the end only if your API requires it.
Is a doubly linked list faster?
It supports backward traversal and can delete with neighboring references, but adds a prev reference and more invariants.
The Bottom Line
Build a custom linked list to understand or exploit node links; otherwise prefer Python list for indexed sequences and collections.deque for fast operations at both ends.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

