Write a Simple Linked List Node in Python: Complete Data Structure Implementation and Fundamentals Guide

Write a simple Linked List Node in python

Write a simple Linked List Node in python

Before I could ever build a full linked list, queue, or tree in Python, I had to understand the humble building block that makes all of them possible: the node. It seems almost too simple to deserve its own article, but I’ve found that truly understanding how a node works — how it holds data, how it references the next node, and how memory is actually allocated for it — is what makes everything built on top of it click. In this guide, I’m going to walk through writing a simple linked list node in Python from scratch, and explain the reasoning behind every design decision.

What Is a Node, Conceptually?

A node is the smallest unit of a linked list. It’s a small container that holds two things:

  1. Data — the actual value I want to store.
  2. A reference (pointer) to the next node in the sequence, or None if it’s the last node.

Unlike arrays, where elements are stored in contiguous memory and accessed by index, a linked list is a chain of these nodes scattered across memory, connected only by references. This is a fundamentally different way of organizing data, and it comes with its own trade-offs that I’ll get into later.

Writing My First Node Class

The most natural way to represent a node in Python is with a class:

class Node:
    def __init__(self, data):
        self.data = data
        self.next = None

That’s genuinely it. This tiny class is the foundation of every linked list, stack, and queue I’ve ever built in Python.

Let me create a few nodes and connect them manually to see how this works:

node1 = Node(10)
node2 = Node(20)
node3 = Node(30)

node1.next = node2
node2.next = node3

current = node1
while current is not None:
    print(current.data)
    current = current.next

Output:

10
20
30

This tiny loop is essentially a manual traversal of a linked list — I start at node1, print its data, then move to whatever next points to, repeating until I hit None.

Adding a Useful repr Method

When I’m debugging, printing a Node object directly isn’t very informative:

print(node1)

Output:

<__main__.Node object at 0x7f8a2d3f4a90>

I usually add a __repr__ method so debugging is easier:

class Node:
    def __init__(self, data):
        self.data = data
        self.next = None

    def __repr__(self):
        return f"Node({self.data!r})"

node1 = Node(10)
print(node1)

Output:

Node(10)

This one small addition has saved me a lot of confusion when inspecting linked list structures in a debugger or console session.

Making the Node Generic and Type-Safe

For larger projects, I like adding type hints to make the code more maintainable and to let tools like mypy catch mistakes early:

from typing import Optional, Any

class Node:
    def __init__(self, data: Any) -> None:
        self.data: Any = data
        self.next: Optional["Node"] = None

    def __repr__(self) -> str:
        return f"Node({self.data!r})"

Using Optional["Node"] (with a forward reference in quotes) is necessary here because at the point the class body is being defined, Node itself doesn’t exist yet as a completed type — Python resolves string-based forward references lazily.

Internal Working: How Python Represents a Node in Memory

Every instance of Node is a full Python object, which means it carries some overhead beyond just the two attributes I defined. Under CPython, each object instance has:

This means a naive Node object is heavier in memory than, say, a raw C struct with two fields would be. I can inspect this with the sys module:

import sys

node = Node(10)
print(sys.getsizeof(node))

The exact number varies between Python versions, but it’s noticeably larger than the size of a simple integer, because of that extra object bookkeeping.

Reducing Memory Overhead with slots

If I’m building a linked list with potentially millions of nodes — say, for a large in-memory dataset — I can significantly reduce memory usage by using __slots__, which tells Python not to create a __dict__ for each instance:

class Node:
    __slots__ = ("data", "next")

    def __init__(self, data):
        self.data = data
        self.next = None

This can cut memory usage per node substantially, since Python no longer needs to allocate a dictionary for instance attributes — it uses a fixed-size array-like structure instead. The trade-off is that I lose the ability to dynamically add new attributes to a node instance that weren’t declared in __slots__.

Node with Additional Metadata

Depending on the use case, I sometimes extend the node to carry more than just a single data value — for example, a priority, a timestamp, or in the case of a doubly linked list, a reference to the previous node as well:

class DoublyNode:
    __slots__ = ("data", "next", "prev")

    def __init__(self, data):
        self.data = data
        self.next = None
        self.prev = None

I’ll cover the full doubly linked list implementation in more detail elsewhere, but it’s worth mentioning here because the node design decision — whether to include a prev reference — is made at this exact stage.

Real-World and Practical Use Cases

Nodes by themselves aren’t very useful, but as the building block for larger structures, they show up everywhere:

Common Mistakes When Writing Node Classes

  1. Forgetting to initialize next to None. If I skip this, I have to remember to set it manually every time I create a node, which is error-prone.
  2. Confusing self.next with the built-in next() function. I make sure never to shadow built-in names with attribute names in a way that causes confusion in the surrounding code.
  3. Not handling circular references properly. If a linked list accidentally becomes circular (the last node points back to an earlier node instead of None), infinite loops during traversal are a very real risk.
  4. Comparing nodes by data instead of identity. If I want to check whether two variables point to the same node object, I use is, not ==, unless I’ve explicitly implemented __eq__.

Debugging Tips

When my linked list traversal misbehaves, the first thing I check is whether every node’s next pointer is set correctly. A simple debug helper I like to use:

def print_list(head):
    nodes = []
    current = head
    seen = set()
    while current is not None:
        if id(current) in seen:
            nodes.append("...(cycle detected)")
            break
        seen.add(id(current))
        nodes.append(repr(current))
        current = current.next
    print(" -> ".join(nodes))

print_list(node1)

This little function protects me from infinite loops when debugging a broken list with an accidental cycle, since it tracks node identities it’s already visited.

Best Practices I Follow

FAQs

Q: Is a Node its own data structure, or just part of one? A node is a building block, not a data structure by itself. A linked list is built from a chain of nodes.

Q: Can I store multiple values in one node? Yes — data can be any Python object, including a tuple, dictionary, or custom object holding multiple fields.

Q: Does Python have a built-in Node class? No, Python doesn’t provide one natively; linked list nodes are typically hand-written or come from third-party data structure libraries.

Q: Why not just use a Python list instead of writing my own linked list nodes? Python’s built-in list is implemented as a dynamic array, which is excellent for random access and appending at the end, but linked lists offer O(1) insertion/removal at arbitrary positions (once you have a reference to the node) without shifting elements.

Troubleshooting Tips

Summary

Writing a simple linked list node in Python is one of those exercises that looks almost trivial but teaches me a lot about how Python objects work under the hood — memory layout, references versus values, and the trade-offs between flexibility and efficiency. Once I have a solid, well-designed Node class, building an entire linked list, stack, queue, or even a tree becomes a matter of composing these small building blocks together.

References

Exit mobile version