Last modified: Aug 16, 2026

Python Dynamic Array Implementation Guide

Dynamic arrays are a fundamental data structure in Python. Unlike static arrays, they can grow and shrink at runtime. This flexibility makes them essential for many applications.

In this guide, you will learn how to build a dynamic array from scratch. We will cover core concepts like memory allocation, resizing, and common operations. By the end, you will have a solid understanding of how Python's list works internally.

What is a Dynamic Array?

A dynamic array is a resizable array data structure. It provides random access to elements like a static array. However, it can automatically expand when you add more elements than its current capacity.

Python's built-in list is a classic example of a dynamic array. When you append items, the list handles the underlying memory management for you. Understanding this process helps you write more efficient code.

Building your own version clarifies how resizing strategies work. It also shows the trade-offs between speed and memory usage. Let's start with the basic class structure.

Creating the Dynamic Array Class

We will implement a class called DynamicArray. It will use a fixed-size Python list internally to store elements. We also track the current size (number of elements) and capacity (allocated space).

The initial capacity can be small, like 4. We will double this capacity whenever we run out of space. This amortized resizing strategy keeps append operations efficient.


class DynamicArray:
    def __init__(self, capacity=4):
        """Initialize the dynamic array with a fixed capacity."""
        self.capacity = capacity
        self.size = 0
        self.data = [None] * capacity  # Internal storage

In the __init__ method, we set up the array. The data attribute holds the actual elements. The size attribute counts how many slots are filled.

Implementing Core Operations

Now let's add methods for basic operations. We need append, get, set, and len. These are the building blocks of any array implementation.

The append method adds an element at the end. If the array is full, we must resize it first. The get and set methods access elements by index. The len method returns the current size.


    def append(self, element):
        """Add an element to the end of the array."""
        if self.size == self.capacity:
            self._resize(self.capacity * 2)  # Double capacity
        self.data[self.size] = element
        self.size += 1

    def get(self, index):
        """Retrieve element at a given index."""
        if index < 0 or index >= self.size:
            raise IndexError("Index out of bounds")
        return self.data[index]

    def set(self, index, element):
        """Replace element at a given index."""
        if index < 0 or index >= self.size:
            raise IndexError("Index out of bounds")
        self.data[index] = element

    def __len__(self):
        """Return the number of elements in the array."""
        return self.size

Notice the append method checks if the array is full. If so, it calls a private _resize method. We will define that next. This ensures we always have room for new elements.

Resizing the Array

Resizing is the heart of a dynamic array. When the array is full, we allocate a new, larger block of memory. Then we copy all existing elements to the new block.

We typically double the capacity. This strategy ensures that each append operation is O(1) on average. The _resize method handles this task safely.


    def _resize(self, new_capacity):
        """Resize the internal storage to a new capacity."""
        new_data = [None] * new_capacity
        for i in range(self.size):
            new_data[i] = self.data[i]
        self.data = new_data
        self.capacity = new_capacity

The _resize method creates a new list with the desired capacity. It then copies each element from the old list. Finally, it updates the data reference and the capacity attribute.

This operation is O(n) because we copy n elements. However, it happens rarely. When it does, it doubles the space, so future appends are fast. This is called amortized analysis.

Adding More Useful Methods

Beyond the basics, we can add methods for insertion and deletion. The insert method adds an element at a specific position. The pop method removes and returns the last element.

These operations require shifting elements. Insertion shifts elements to the right. Deletion shifts elements to the left. Let's implement them carefully.


    def insert(self, index, element):
        """Insert an element at a specific index."""
        if index < 0 or index > self.size:
            raise IndexError("Index out of bounds")
        if self.size == self.capacity:
            self._resize(self.capacity * 2)
        # Shift elements to the right
        for i in range(self.size, index, -1):
            self.data[i] = self.data[i - 1]
        self.data[index] = element
        self.size += 1

    def pop(self):
        """Remove and return the last element."""
        if self.size == 0:
            raise IndexError("Pop from empty array")
        element = self.data[self.size - 1]
        self.data[self.size - 1] = None  # Clear reference
        self.size -= 1
        # Shrink if too empty (optional)
        if self.size < self.capacity // 4:
            self._resize(self.capacity // 2)
        return element

The insert method first checks bounds and resizes if needed. Then it shifts elements from the end to the target index. Finally, it places the new element and increments the size.

The pop method removes the last element. It also clears the old reference to help garbage collection. Optionally, we shrink the array when it's too empty to save memory.

Testing the Implementation

Let's test our dynamic array with a simple example. We will append several elements and observe how the capacity grows. Then we will try insert and pop operations.


# Create a dynamic array
arr = DynamicArray()
print(f"Initial size: {len(arr)}, capacity: {arr.capacity}")

# Append elements
for i in range(1, 7):
    arr.append(i * 10)
    print(f"After append {i}: size={len(arr)}, capacity={arr.capacity}")

# Access elements
print("Element at index 2:", arr.get(2))

# Insert at position 1
arr.insert(1, 99)
print("After insert:", [arr.get(i) for i in range(len(arr))])

# Pop last element
last = arr.pop()
print("Popped:", last)
print("Final array:", [arr.get(i) for i in range(len(arr))])

Run this code to see how the array behaves. You will notice the capacity doubles when needed. This demonstrates the resizing strategy in action.


Initial size: 0, capacity: 4
After append 1: size=1, capacity=4
After append 2: size=2, capacity=4
After append 3: size=3, capacity=4
After append 4: size=4, capacity=4
After append 5: size=5, capacity=8
After append 6: size=6, capacity=8
Element at index 2: 30
After insert: [10, 99, 20, 30, 40, 50, 60]
Popped: 60
Final array: [10, 99, 20, 30, 40, 50]

As you can see, the array grows from capacity 4 to 8 on the fifth append. This is the dynamic behavior we wanted. The insert and pop operations work correctly too.

Performance Considerations

Dynamic arrays offer O(1) average time for append and pop operations. However, worst-case resizing is O(n). This is acceptable because resizing happens infrequently.

Accessing an element by index is always O(1). This makes dynamic arrays excellent for random access. For insertion and deletion at arbitrary positions, they are O(n) due to shifting.

If you need frequent insertions at the beginning, consider using a deque or linked list. For most use cases, a dynamic array is the right choice. Python's list is highly optimized for this purpose.

Comparing with Python's Built-in List

Our implementation is a simplified version of Python's list. The built-in list uses a similar resizing strategy. However, it is written in C and heavily optimized for performance.

Python's list also supports slicing, iteration, and many other methods. Our custom class focuses on the core mechanics. This helps you understand what happens under the hood.

To learn more about how Python allocates memory for arrays, check out our Python Array Memory Allocation Explained guide. It dives deeper into the memory management aspects.

Common Use Cases

Dynamic arrays are everywhere in Python. They are used for stacks, queues, and as general-purpose sequences. Understanding their behavior helps you choose the right data structure.

For example, you can use a dynamic array to implement a stack. Append and pop operations at the end are perfect for LIFO behavior. Similarly, you can build a queue using two arrays.

Our custom implementation is educational. In real projects, you should always use Python's built-in list. It is faster, safer, and more feature-rich. But knowing the internals makes you a better programmer.

Edge Cases and Error Handling

Our implementation handles several edge cases. We raise IndexError for invalid indices. We also check for empty array pops. These safeguards prevent silent bugs.

One thing to note is the shrink logic in pop. We only shrink when the size drops below a quarter of the capacity. This prevents frequent resizing when the array oscillates around a threshold.

You can adjust these thresholds based on your needs. For instance, a smaller shrink threshold saves more memory but may cause more resizing. A larger threshold wastes memory but reduces resizing overhead.

Conclusion

In this guide, you learned how to implement a dynamic array in Python. We covered the class structure, core operations, and resizing logic. You also saw how to test and optimize the implementation.

Dynamic arrays are a powerful tool in any programmer's toolkit. They combine the speed of arrays with the flexibility of dynamic resizing. Now you know exactly how they work.

To further enhance your understanding, explore related topics like array type casting and iteration. Read our Python Array Type Casting Guide to learn about converting data types. Also, check out Python Array Unpacking: A Clear Guide for advanced techniques.

Keep practicing by adding more methods like remove or clear. This will solidify your knowledge. Happy coding!