Introduction
If you are looking to import SortedList in Python, you’re tapping into a powerful data structure that keeps your sequences automatically sorted. SortedList comes from the sortedcontainers library, a third‑party package that adds high‑performance, ordered collections to the Python ecosystem. While Python’s built‑in list type is simple and ubiquitous, it does not maintain any order beyond the sequence you provide. SortedList fills that gap by offering O(log n) insertion and lookup while preserving a sorted order, which is especially useful for tasks like maintaining a priority queue, implementing sliding windows, or handling large datasets where constant‑time access to the smallest or largest element is required. In this article we’ll walk you through the exact steps to import SortedList in Python, show you how to use it effectively, and explain why it can outperform a regular list in many scenarios.
How to Install sortedcontainers
Before you can import SortedList in Python, the sortedcontainers package must be installed in your environment. The installation is straightforward using pip:
- Open a terminal or command prompt.
- Run the following command:
pip install sortedcontainers - Verify the installation by checking the package version:
import sortedcontainers print(sortedcontainers.__version__)
If you are using a virtual environment, make sure it is activated before running the install command. Once installed, you can proceed to the import step.
How to Import SortedList in Python
After installation, importing SortedList is as simple as importing any other module:
from sortedcontainers import SortedList
This single line brings the SortedList class into your namespace, allowing you to create instances like a regular Python list:
# Create an empty SortedList
sl = SortedList()
# Populate it with numbers; they will be kept sorted automatically
sl.add(5)
sl.add(2)
sl.add(9)
print(sl) # Output: SortedList([2, 5, 9])
The import statement is the cornerstone of using SortedList, so remember to keep it at the top of your script or notebook for clarity and to avoid runtime errors.
Basic Operations with SortedList
SortedList mimics many list methods, but with added sorting guarantees. Below are the most common operations you’ll perform after importing SortedList in Python:
Creation and Population
- Empty SortedList:
sl = SortedList() - From an iterable:
sl = SortedList([3, 1, 4])– the elements are sorted on creation.
Adding Elements
sl.add(value)– insertsvaluewhile preserving order (O(log n)).sl.update(iterable)– adds multiple items efficiently.
Accessing Elements
sl[0]– retrieves the smallest element (O(1)).sl[-1]– retrieves the largest element (O(1)).- Slicing works similarly to a list, but slices are also sorted.
Removing Elements
sl.remove(value)– removes the first occurrence ofvalue(O(log n)).sl.discard(value)– removesvalueif present; does nothing otherwise.sl.clear()– empties the container.
Searching and Counting
sl.bisect_left(value)andsl.bisect_right(value)– return insertion points for binary search.sl.count(value)– returns the number of occurrences (useful for multisets).
Iteration and Length
for item in sl:– iterates in ascending order.len(sl)– returns the number of elements.
Example: Maintaining a Running Median
A classic use case for SortedList is keeping a running median. As new numbers arrive, you can insert them in O(log n) time and retrieve the middle element in O(1) by indexing:
from sortedcontainers import SortedList
def running_median(stream):
sl = SortedList()
medians = []
for value in stream:
sl.add(value)
medians.append(sl[len(sl)//2] if len(sl) % 2 == 1 else
(sl[len(sl)//2 - 1] + sl[len(sl)//2]) / 2)
return medians
# Example usage
stream = [3, 1, 4, 1, 5, 9, 2, 6]
print(running_median(stream))
This snippet demonstrates how import SortedList in Python can simplify algorithms that require ordered data.
When to Use SortedList vs. List
Choosing between a regular Python list and SortedList depends on your performance needs and the operations you’ll perform most often:
| Operation | Python List | SortedList |
|---|---|---|
| Append (unsorted) | O(1) | O(log n) |
| Insert at arbitrary position | O(n) | O(log n) (via add) |
| Find min / max | O(n) (scan) | O(1) (index) |
| Binary search | O(n) (linear) | O(log n) (bisect) |
| Maintain sorted order | Requires manual sorting | Automatic |
If your workflow involves frequent insertions and you need instant access to the smallest or largest element, SortedList is the superior choice. Still, for simple iteration or when you never need ordering, a plain list may be more memory‑efficient and faster for bulk operations.
Scientific Explanation of Performance
SortedList is built on a balanced binary search tree (BST) structure, often implemented as a treap or skip list under the hood. This design guarantees that the height of the tree remains logarithmic relative to the number of elements, which explains the O(log n) insertion and lookup times. In contrast, Python’s list is a dynamic array; inserting at the beginning or middle requires shifting all subsequent elements, leading to O(n) complexity.
Because SortedList maintains order internally, you avoid the overhead of repeatedly calling list.sort() after each insertion, which would be O(n log n) per operation in a naive approach. The memory footprint of SortedList is slightly higher than that of a list due to additional tree nodes, but the trade‑off is often worthwhile when order and fast access are critical.
And yeah — that's actually more nuanced than it sounds.
Common Pitfalls and Best Practices
- Do not mix types without care: SortedList compares elements using Python’s default ordering, which can raise TypeError when comparing incompatible types (e.g.,
intvs.str). Ensure homogeneous data or define a custom key
Additional Pitfalls to Watch For
-
Mutating Elements After Insertion
Once an object is placed inside a SortedList, its value should remain immutable. If you later modify an element that is already stored, the underlying tree may become inconsistent because the ordering logic no longer matches the stored key. The safest approach is to treat the stored items as read‑only after they have been added. -
Reliance on Default Comparison for Complex Types
Python’s default comparison falls back to the “less‑than” operator. For custom classes this often means invoking__lt__, which can be error‑prone if the method does not implement a total ordering. When the class defines__eq__but not__lt__, objects may appear equal while still being considered different, leading to surprising duplicate‑removal behavior. Explicitly defining__lt__(and optionally__le__) removes this ambiguity. -
Memory Overhead in Massive Streams
Each node in the underlying balanced tree carries additional pointers, which can increase the memory footprint by roughly 30‑40 % compared with a plain list. In scenarios where the stream contains millions of numbers, this overhead may become a limiting factor. In such cases, a hybrid strategy — maintaining a compact list for short‑term buffering and flushing to a SortedList only when the median is required — can preserve both speed and space efficiency Still holds up.. -
Non‑Thread‑Safe Access
The data structure is not protected against concurrent modifications. Simultaneousaddorremoveoperations from different threads can corrupt the internal tree. If you need a SortedList in a multithreaded program, wrap all mutating calls in a lock (e.g.,threading.Lock) or use a process‑safe container such as a manager‑based list from the multiprocessing module No workaround needed.. -
Inefficient Bulk Inserts
Because each insertion triggers a logarithmic rebalancing step, inserting a large batch of values one‑by‑one results in O(k log n) time for k elements. For bulk loading, it is faster to construct a temporary list, sort it once with the built‑insortedfunction (which is O(n log n) but highly optimized in C), and then feed the resulting ordered sequence into the SortedList via itsupdatemethod, which can amortize the cost That's the part that actually makes a difference..
Best‑Practice Checklist
- Keep the collection homogeneous – ensure every element is of the same type or implements a total ordering.
- Prefer immutable values – avoid mutating objects after they have been inserted.
- Batch inserts when possible – build a sorted list in memory first, then call
sl.update(new_items)to reduce the number of rebalancing operations. - Guard mutable items – if you must store mutable objects, copy them before insertion or wrap them in an immutable container (e.g., a tuple).
- Use explicit locks for concurrency – protect all write operations with a mutex to preserve consistency.
- make use of slicing for range queries –
sl[lo:hi]returns a view that is itself a SortedList, enabling efficient extraction of sub‑ranges without extra copying. - Monitor memory usage – for extremely large datasets, consider alternative structures (e.g., two heaps for median maintenance) that trade a bit of order‑maintenance overhead for lower memory consumption.
Conclusion
SortedList offers a clean, logarithmic‑time solution for problems that demand a continuously ordered collection, such as streaming medians, real‑time rank queries, or any algorithm where the median or other order statistics must be retrieved instantly after each update. Its underlying balanced‑tree design guarantees predictable performance, while its Pythonic interface eliminates the need for manual heap management or repeated sorting calls Simple, but easy to overlook. No workaround needed..
That said, the structure is not a universal replacement for a plain list. By understanding the specific trade‑offs — time complexity, memory overhead, thread safety, and the nature of the data — developers can decide when SortedList is the optimal tool and apply the recommended best practices to avoid common pitfalls. Worth adding: when the workload consists mainly of bulk operations, infrequent reordering, or when memory is at a premium, a regular list may still be the more efficient choice. This balance of speed, reliability, and simplicity makes SortedList a valuable asset in the Python programmer’s toolbox.