Python len() Explained: Behavior and Performance
python **len**: Understand how Python's len() works across built-in types, its O(1) behavior, and how to support it in custom classes.
The len() function is one of the most frequently used built-ins in Python, yet its behavior is not always fully understood. When you call len(container), Python does not iterate over the container to count elements. Instead, it relies on an internal protocol that each type implements. This article explains how python **len** works for built-in types, why it is constant time for common containers, and how to make custom objects compatible with it.
How len() Works for Built-in Types
For built-in types such as list, str, tuple, bytes, and dict, len() returns the number of items stored in the object. The function does not scan the object; it reads a pre-maintained size attribute. Lists and strings store their length as part of their internal structure, so retrieving it is a direct attribute lookup. For example:
fruits = ["apple", "banana", "cherry"] print(len(fruits)) # 3 text = "hello" print(len(text)) # 5
For dictionaries, len() returns the number of key-value pairs. For sets and frozensets, it returns the number of unique elements. In all these cases, the operation is O(1) because the size is tracked during mutation operations like append, insert, or __setitem__.
The O(1) Guarantee for Common Containers
Unlike a function that must traverse the entire structure, len() for built-in sequence and mapping types does not depend on the number of elements. This is a direct consequence of how CPython implements these objects. Each list object contains a ob_size field, and each string stores its length in the header. For dictionaries and sets, the number of entries is maintained as a count. Therefore, calling len() on a list with a million elements takes the same time as calling it on an empty list.
This behavior is part of the language specification for these types. The documentation states that len() returns the number of items of a container, and for built-in types the implementation is guaranteed to be O(1). This guarantee is what allows patterns like if len(items) > 0 to be used freely in performance-sensitive code without worrying about accidental iteration.
Using len() with User-Defined Objects via len
You can make any custom class compatible with len() by implementing the __len__ method. This method must return a non-negative integer. Python will call it whenever len() is invoked on an instance of that class. Here is a minimal example:
class Playlist: def __init__(self, tracks): self._tracks = list(tracks) def __len__(self): return len(self._tracks) playlist = Playlist(["Song A", "Song B"]) print(len(playlist)) # 2
When len() is called, Python looks up the __len__ method on the type and calls it. If the method returns a non-integer, Python raises a TypeError. If it returns a negative value, Python raises a ValueError. This protocol is the same one used by built-in types, so your custom objects can participate in any code that expects a sized object.
Common Mistakes When Calling len()
One frequent error is calling len() on an iterator. Iterators such as those returned by iter() or generators do not have a length because they are consumed lazily. For example:
numbers = [1, 2, 3] it = iter(numbers) print(len(it)) # TypeError: object of type 'list_iterator' has no len()
To get the number of remaining items in an iterator, you must consume it and count, which is O(n) and may have side effects. Another mistake is assuming len() works on None or on numbers. len(None) raises TypeError, and len(42) also raises TypeError. The function is only defined for objects that have a __len__ method.
A subtler issue arises with boolean values. In Python, bool is a subclass of int, but it does not implement __len__. So len(True) fails. Always check that the object actually supports len() before using it in generic code, especially when handling data from external sources.
Performance Implications of len() in Loops and Conditions
Because len() is O(1) for built-in containers, using it inside a loop condition is safe. For instance, the following loop is efficient:
def process(items): for i in range(len(items)): item = items[i] # process item
However, if you call len() repeatedly on a custom object whose __len__ implementation is expensive, the cost can accumulate. For example, if __len__ recalculates a value by iterating over an internal structure, then calling it in every loop iteration turns an O(1) loop into O(n²).
class SlowPlaylist: def __len__(self): # Simulates an expensive calculation return sum(1 for _ in self._tracks)
In such cases, cache the result once before the loop:
n = len(playlist) for i in range(n): # safe
This is a practical concern when you control the __len__ implementation. For built-in types, the cost is negligible, but for custom classes you should ensure that __len__ is O(1) if it will be called frequently.
len() vs. Alternative Length Checks
Sometimes developers use if container: instead of if len(container) > 0: to check for non-empty containers. Both are valid for built-in types, but they are not exactly equivalent. The truthiness check calls __bool__ if defined, and falls back to __len__ if __bool__ is not defined. For most built-in containers, __bool__ is defined and returns True if the container is non-empty, which is the same as len() > 0. However, for custom classes that implement __bool__ differently, the two checks can diverge.
For example, a class might define __bool__ to return False even when it has items, or vice versa. If you want to specifically test for the number of elements, use len() directly. If you want to test for truthiness according to the object's own logic, use the implicit boolean conversion. This distinction matters when writing generic code that must respect an object's intended semantics.
When len() Raises TypeError
The len() function raises a TypeError when the object does not have a __len__ method. This includes numbers, None, and most objects that are not containers. The error message is usually clear: object of type 'int' has no len(). In some cases, an object may have a __len__ method but the method itself raises an exception. That exception propagates to the caller. For example, if __len__ raises a ValueError because the internal state is inconsistent, len() will surface that error.
When writing code that accepts arbitrary objects, you can guard against TypeError by using hasattr(obj, '__len__') or by catching the exception. However, checking hasattr is not always reliable if __len__ is dynamically provided. Prefer duck-typing: assume the object supports len() and handle the TypeError if it does not. This keeps the code explicit about the expected interface.