Skip to main content

How to Pretty Print a Linked List in Python

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:

MethodCalled ByPurpose
__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) + "]"


# Usage
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)

# __str__ is called by print()
print(ll)

# __repr__ is shown in the console and containers
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])]
__str__ vs. __repr__
  • __str__: Called by print() and str(). Should be human-readable.
  • __repr__: Called by the interactive console, repr(), and when the object is inside a container (like a list). Should be unambiguous and ideally show enough information to recreate the object.

If only one is defined, define __repr__. Python falls back to __repr__ when __str__ is not available, but not vice versa.

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)

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

# Empty list
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:

# ❌ Without __str__
class BasicLinkedList:
def __init__(self):
self.head = None

ll = BasicLinkedList()
print(ll)
# <__main__.BasicLinkedList object at 0x7f8b8c0a1d90>

With __str__, you get clean, informative output:

# ✅ With __str__
print(ll)
# [10, 15, 20]

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.