Introduction
Welcome to your second lesson! In our previous session, we established the critical importance of Big-O notation for analyzing algorithm efficiency. You learned how to determine an algorithm's scalability by focusing on its growth rate and simplifying complexity expressions.
Today, we'll apply that analytical framework to our first core data structure: the dynamic array. In Python, you've used this every day as the ubiquitous list. While it's easy to use, understanding how it works internally is crucial for writing truly efficient code and for answering deep-dive questions in a FAANG interview. Your experience with system architecture will be valuable here, as we're essentially looking at the design trade-offs made to create a flexible, high-performance data structure.
By the end of this 60-minute lesson, you will be able to implement a basic dynamic array and, more importantly, explain the time complexity of its common operations like appending, inserting, and accessing elements.
1. From Static to Dynamic: The "Why"
Before we can appreciate the "dynamic" nature of Python's list, we need to refresh our understanding of its foundation: the static array. A static array is a fixed-size, contiguous block of memory. This structure is what allows for the magical O(1) or constant time access to any element by its index.
However, its fixed size is also its greatest weakness. What happens when you need to add more elements than you initially allocated space for? This limitation is what gives rise to the dynamic array.
Let's watch a video that explains this evolution from static to dynamic arrays and introduces the core concepts of resizing and amortized analysis.
Static Arrays, Dynamic Arrays, and Strings - Big O Complexity - DSA Course in Python Lecture 2
This video by Greg Hogg provides a clear explanation of static arrays, their limitations, and how dynamic arrays (like Python's list) are built upon them to overcome these limitations.
Please watch the following two segments: Static Arrays (00:00 - 04:21): Pay attention to why access is O(1) and why insertion/deletion is O(n). Dynamic Arrays (04:21 - 11:11): Focus on the mechanism of resizing (copying to a new, larger array) and how this leads to an amortized O(1) time complexity for appending elements.
As the video explained, the "dynamic" aspect is an abstraction. Under the hood, when a dynamic array runs out of space, it performs a costly O(n) operation:
- Allocate a new, larger block of memory.
- Copy all elements from the old block to the new one.
- Add the new element.
The key is that this resizing happens infrequently. The cost is "amortized" or spread out over many cheap O(1) appends, making the average cost of appending effectively O(1).
2. The Internals of a Python list
Python's list has a clever implementation that you should be aware of. While static arrays require all elements to be the same size for index calculations to work, a Python list can famously hold elements of different types (integers, strings, objects, etc.).
How does it achieve this while still providing O(1) access? It does so by storing an array of references (or pointers), not the actual objects themselves. Each reference is a fixed size (e.g., 8 bytes on a 64-bit system), so the "all elements are the same size" rule is upheld for the array of references. The references then point to the actual objects, which can be of varying sizes and located anywhere in memory.

This distinction between the list's size (number of elements) and its capacity (allocated memory) is fundamental to its performance.
3. Implementing a Dynamic Array
A common interview question is to implement a dynamic array from scratch. This tests your understanding of the underlying mechanics. Let's walk through an implementation to solidify these concepts.
Design a Dynamic Array (Resizable Array)
This video from NeetCodeIO guides you through building a dynamic array class in Python. This exercise is invaluable for truly understanding the data structure.
Watch the video from the beginning to the end (00:00 - 11:07). I recommend pausing at each method (push_back, resize, pop_back) and thinking about how you would implement it before watching the solution. Pay close attention to the final time complexity analysis at the end (08:35 onwards), as it recaps everything we've discussed.
This implementation brings together all the key ideas:
- Maintaining
sizeandcapacityvariables. - Checking
if self.size == self.capacitybefore an append (push_back). - Implementing a
_resizemethod that doubles capacity and copies elements. - Understanding that
pop_backis a "soft deletion" that simply decrements thesizepointer, making it an O(1) operation.
4. Summary of list Time Complexities
You now have the theoretical and practical knowledge to understand the performance of Python's built-in list methods. It's crucial to have these complexities memorized for interviews.
The following table provides a concise summary.

To reinforce this, here's a quick textual summary of the most important operations:
Fast Operations: O(1)
- Access:
lst[i] - Update:
lst[i] = val - Length:
len(lst) - Append:
lst.append(item)(Amortized O(1)) - Pop from end:
lst.pop()
Slow Operations: O(n)
- Insert:
lst.insert(i, item)(Requires shifting elements) - Pop from middle/start:
lst.pop(i)(Requires shifting elements) - Search:
item in lst(Requires scanning the list) - Remove:
lst.remove(item)(Requires a search, then a shift)
5. Thought Experiment
Let's connect this to a practical scenario. Imagine you're designing a system to track user activity for a live web application. You decide to store recent activity events in a Python list.
-
Scenario A: Your primary operations are adding new events as they occur and occasionally displaying the total number of events. Which
listmethods would you use, and why is this an efficient design based on their time complexity? -
Scenario B: Now, imagine a new requirement: you must always keep the events sorted by timestamp. Every new event has to be inserted into its correct chronological position in the list. What is the time complexity of adding a new event in this design? What potential performance issues might arise if you have a high volume of events?
(Take a moment to think before reading the solution below).
Solution:
- Scenario A: You would use
list.append()to add new events andlen(list)to get the count. This is highly efficient because bothappend()(amortized) andlen()are O(1) operations. The system can handle a high rate of new events. - Scenario B: To insert an event in the middle, you'd likely find the correct position and use
list.insert(idx, event). This is an O(n) operation because all subsequent elements must be shifted. With a high volume of events, the linear time cost of insertion would become a major performance bottleneck. This tells you that for this use case, a simplelistmight not be the right data structure. We'll explore better alternatives (like heaps or balanced binary search trees) later in the course.
Conclusion
Today, we've peeled back the layers of Python's list to reveal the dynamic array underneath. You've moved from simply using this data structure to understanding its internal design and performance trade-offs.
Key Takeaways:
- Python's
listis a dynamic array, built upon a static array of references. - O(1) index access is possible because the internal array of references is a contiguous block of memory.
- Appending is amortized O(1). While resizing is an O(n) operation, its cost is spread across many O(1) appends, making it efficient on average.
- Insertion or deletion at the start or middle is O(n) because it requires shifting all subsequent elements. Knowing this is key to avoiding performance bottlenecks in your code.
Preview of the Next Lesson
Now that you have a firm grasp of how dynamic arrays work, you're ready to start solving problems with them. In the next lesson, we will focus on a common category of interview questions: in-place array manipulation. You'll learn techniques to modify arrays directly, without using extra memory, to solve problems like removing duplicates and rotating an array.