How is Python's List Implemented?
Data Structures & Algorithms practice on Codemia
Step through 300 algorithm problems with animated visualisers that show the data structure changing as the code runs.
Introduction
Python's list is implemented as a dynamic array, not as a linked list. More specifically, in CPython it stores a contiguous array of references to Python objects, which explains why indexing is fast, appending is usually cheap, and inserting near the front is expensive.
A List Stores References, Not Inline Objects
A Python list does not embed full object values directly next to each other in raw C-style structure. Instead, it stores references to Python objects.
That means a list like this:
contains a contiguous array of pointers or references, each pointing to a separately managed Python object.
This design is what allows Python lists to hold mixed types so easily.
Why Indexing Is Fast
Because the internal storage is array-based, Python can compute the address of an element by index arithmetic.
Random access is therefore O(1).
That is one of the main reasons lists are the default general-purpose sequence type in Python.
Why Appending Is Usually Fast
If lists were resized on every append, they would be painfully inefficient. Instead, CPython over-allocates extra capacity.
So when you do:
most appends do not require allocating a new array. Python grows the internal storage in chunks, which makes append() amortized O(1) instead of O(n) every time.
Occasionally a resize still happens. When it does, Python allocates a larger block, copies the references, and releases the old block.
Why Insert and Delete in the Middle Are Slower
An array-based list keeps elements in positional order. If you insert near the beginning or delete from the middle, later elements must shift.
That shift makes such operations O(n).
The same logic explains why pop() from the end is cheap while removing from the front is relatively expensive.
Slicing Creates a New List
When you slice a list, Python builds a new list containing references to the selected elements.
That takes time proportional to the slice length, not constant time. The elements themselves are not deep-copied automatically, but the new list still needs its own array of references.
Compare Lists With Other Sequence Types
Understanding the implementation helps you choose the right structure.
Use a list when you need:
- fast indexed access
- efficient append at the end
- a flexible mutable sequence
A deque is better when you need frequent insertion or removal at both ends.
A tuple is better when immutability matters.
A NumPy array is better for large homogeneous numeric data and vectorized operations.
Lists are versatile, but they are not the best answer to every sequence problem.
CPython Details Versus Python the Language
When people ask how Python lists are implemented, the answer usually refers to CPython, the reference implementation most people run.
Other Python implementations may differ internally, but the user-visible complexity expectations are broadly similar: fast indexing, amortized append, slower middle insertions, and so on.
That is why it is useful to distinguish between Python-the-language semantics and CPython-specific implementation details.
Common Pitfalls
The most common mistake is thinking Python lists are linked lists because insertion feels flexible at the language level. They are not.
Another mistake is assuming all operations are equally cheap just because the syntax is simple.
A third issue is forgetting that lists store references. Copying a list does not deep-copy the contained objects.
Finally, if your workload depends on fast front insertions or removals, switching to collections.deque may matter more than micro-optimizing list code.
Summary
- Python lists are dynamic arrays of object references.
- Indexed access is
O(1)because the storage is contiguous. - Appending is amortized
O(1)due to over-allocation. - Inserting or deleting in the middle is
O(n)because elements shift. - Slicing creates a new list of references.
- Lists are a strong default sequence type, but not the right choice for every access pattern.
Related reading
- How is set implemented?
- How is the complexity of bucket sort is Onk if we implement buckets using linked lists?
- How is the default max Java heap size determined?
- How is vectorvectorint heavier than vectorpairint,int?
- How is str.joiniterable method implemented in Python/ Linear time string concatenation
- How is the R2 value in Scikit learn calculated?
- How list Amazon S3 bucket contents by modified date?
- How long the messages will be kept stored in topic/partition using kafka 0.9

DSA Fundamentals
Master algorithmic patterns and data structures through hands-on LeetCode-style problems - from arrays and hashing to dynamic programming and advanced graphs.
View the courseTrack what you have practised
A free account saves your progress, solutions and study plan across every problem on Codemia.
Data Structures & Algorithms practice on Codemia
Step through 300 algorithm problems with animated visualisers that show the data structure changing as the code runs.