Python
List Implementation
Data Structures
Programming
Python Internals

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.

Practice algorithms

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:

python
values = [10, "hi", [1, 2]]

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.

python
values = ["a", "b", "c", "d"]
print(values[2])

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:

python
items = []
for i in range(1000):
    items.append(i)

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.

python
values = [1, 2, 3, 4]
values.insert(1, 99)
print(values)

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.

python
values = [1, 2, 3, 4, 5]
part = values[1:4]
print(part)

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
Course
Intermediate
27 lessons
15 hours
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 course
Track 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.

Practice algorithms

All Rights Reserved.