Skip to content

How to Pretty Print a Linked List in Python

Learn how to use Python's __str__ and __repr__ dunder methods to create clean, readable linked list outputs with print(), matching built-in types.

When building custom data structures in Python, the default output from print() is often unhelpful: typically showing something like <__main__.LinkedList object at 0x7f...>. By leveraging Python's dunder methods (double underscore methods), you can make your linked list display beautifully with the standard print() function, just like built-in types.

In this guide, you will learn how to use Python's __str__ and __repr__ methods to pretty print linked lists in a clean, readable format.

What Are Dunder Methods?

Dunder methods (short for "double under" methods) are special methods in Python that start and end with double underscores, like __init__, __str__, and __repr__. They allow you to define how your custom objects behave with built-in Python operations.

The two most relevant dunder methods for pretty printing are:

Method Called By Purpose
__str__ print(), str() Human-readable representation
__repr__ Interactive console, repr() Unambiguous developer representation

When you call print(my_object), Python internally calls my_object.__str__() and prints the returned string.

Basic Pretty Print: List-Style Output

Here is a complete linked list implementation with a __str__ method that formats the output like a Python list:

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


class LinkedList:
    def __init__(self):
        self.head = None

    def append(self, val):
        """Add a node to the end of the list."""
        new_node = Node(val)
        if self.head is None:
            self.head = new_node
            return
        current = self.head
        while current.next:
            current = current.next
        current.next = new_node

    def __str__(self):
        """Return a list-style string representation."""
        elements = []
        current = self.head
        while current:
            elements.append(str(current.val))
            current = current.next
        return "[" + ", ".join(elements) + "]"


ll = LinkedList()
ll.append(10)
ll.append(15)
ll.append(20)

print(ll)

Output:

[10, 15, 20]

The print() function automatically calls __str__(), which traverses the linked list, collects all values, and returns a formatted string.

Arrow-Style Output

For a more visual representation that emphasizes the linked structure, use arrows between nodes:

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


class LinkedList:
    def __init__(self):
        self.head = None

    def append(self, val):
        new_node = Node(val)
        if self.head is None:
            self.head = new_node
            return
        current = self.head
        while current.next:
            current = current.next
        current.next = new_node

    def __str__(self):
        """Return an arrow-style string representation."""
        elements = []
        current = self.head
        while current:
            elements.append(str(current.val))
            current = current.next

        if elements:
            return " -> ".join(elements) + " -> None"
        return "Empty LinkedList"


ll = LinkedList()
ll.append(10)
ll.append(15)
ll.append(20)

print(ll)

Output:

10 -> 15 -> 20 -> None

This format clearly shows the chain of nodes and the None terminator at the end.

Adding __repr__ for Developer Context

While __str__ provides a user-friendly display, __repr__ gives a more detailed, developer-focused representation. It is shown in the interactive console and when objects are inside containers:

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


class LinkedList:
    def __init__(self):
        self.head = None

    def append(self, val):
        new_node = Node(val)
        if self.head is None:
            self.head = new_node
            return
        current = self.head
        while current.next:
            current = current.next
        current.next = new_node

    def __len__(self):
        """Return the number of nodes."""
        count = 0
        current = self.head
        while current:
            count += 1
            current = current.next
        return count

    def __str__(self):
        elements = []
        current = self.head
        while current:
            elements.append(str(current.val))
            current = current.next
        return "[" + ", ".join(elements) + "]"

    def __repr__(self):
        return f"LinkedList(length={len(self)}, values={self.__str__()})"


ll = LinkedList()
ll.append(10)
ll.append(15)
ll.append(20)

print(ll)

print(repr(ll))
print([ll])  # When inside a list, __repr__ is used

Output:

[10, 15, 20]
LinkedList(length=3, values=[10, 15, 20])
[LinkedList(length=3, values=[10, 15, 20])]

Pretty Printing an Empty List

Always handle the edge case of an empty linked list:

ll = LinkedList()
print(ll)

Output:

[]

The __str__ method returns "[]" when the list has no nodes, matching the behavior of an empty Python list.

Complete Implementation With Multiple Display Formats

Here is a full implementation that supports multiple output styles:

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


class LinkedList:
    def __init__(self):
        self.head = None

    def append(self, val):
        new_node = Node(val)
        if self.head is None:
            self.head = new_node
            return
        current = self.head
        while current.next:
            current = current.next
        current.next = new_node

    def __len__(self):
        count = 0
        current = self.head
        while current:
            count += 1
            current = current.next
        return count

    def __str__(self):
        """List-style: [10, 15, 20]"""
        elements = []
        current = self.head
        while current:
            elements.append(repr(current.val))
            current = current.next
        return "[" + ", ".join(elements) + "]"

    def __repr__(self):
        return f"LinkedList({self.__str__()})"

    def to_arrow_string(self):
        """Arrow-style: 10 -> 15 -> 20 -> None"""
        elements = []
        current = self.head
        while current:
            elements.append(str(current.val))
            current = current.next
        if elements:
            return " -> ".join(elements) + " -> None"
        return "Empty"


if __name__ == "__main__":
    ll = LinkedList()
    ll.append(10)
    ll.append(15)
    ll.append(20)

    print(f"List style:  {ll}")
    print(f"Arrow style: {ll.to_arrow_string()}")
    print(f"Repr:        {repr(ll)}")
    print(f"Length:      {len(ll)}")

    empty = LinkedList()
    print(f"\nEmpty list:  {empty}")
    print(f"Empty arrow: {empty.to_arrow_string()}")

Output:

List style:  [10, 15, 20]
Arrow style: 10 -> 15 -> 20 -> None
Repr:        LinkedList([10, 15, 20])
Length:      3

Empty list:  []
Empty arrow: Empty

Pretty Printing the Node Class Too

You can also add __str__ to the Node class for individual node display:

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

    def __str__(self):
        return f"Node({self.val})"

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


node = Node(42)
print(node)       # Node(42)
print(repr(node)) # Node(42)

Output:

Node(42)
Node(42)

Why This Matters

Without __str__, printing a linked list produces useless output:

class BasicLinkedList:
    def __init__(self):
        self.head = None

ll = BasicLinkedList()
print(ll)

With __str__, you get clean, informative output:

print(ll)

This makes debugging, logging, and interactive development significantly easier.

Conclusion

Pretty printing a linked list in Python is accomplished by implementing the __str__ dunder method in your linked list class.

When print() is called on an instance, Python automatically invokes __str__(), which traverses the list, collects node values, and returns a formatted string.

Add __repr__ for developer-facing output, and consider providing multiple display formats (list-style, arrow-style) for different contexts.

These dunder methods are a powerful Pythonic pattern that makes custom data structures behave naturally with built-in functions: no separate print utilities needed.

Navigation