An array is a data structure that stores a collection of items of the same type, such as integers or strings. These items are stored contiguously, much like a row of numbered lockers. Each item in an array can be quickly accessed using its index, so reading or updating any element is fast.
Arrays support a handful of common operations. Because array elements are stored together in memory, some are very fast, while others take more work. Here is an overview of array operations:
| Operations | Time | Space |
|---|---|---|
| Append | O(1) | O(1) |
| Pop | O(1) | O(1) |
| Access | O(1) | O(1) |
| Insert | O(n) | O(1) |
| Delete | O(n) | O(1) |
| Search | O(n) | O(1) |
| Resize | O(n) | O(n) |
Pointers are useful because they let you directly access and modify array elements. That direct access is what makes arrays fast, especially when iterating through elements or building data structures like stacks and queues. Here is an example:
def reverse(array: List[int]) -> List[int]:
left, right = 0, len(array) - 1
while left < right:
array[left], array[right] = array[right], array[left]
left += 1
right -= 1
return array
# Example usage
my_array = [1, 2, 3, 4, 5, 6]
print(reverse(my_array)) # Output: [6, 5, 4, 3, 2, 1]

The reverse() function above takes an array of integers and flips it in place, meaning the original array is modified without needing extra space for another array. It uses two pointers: left starts at the beginning, and right starts at the end. They walk toward each other, swapping elements along the way, until they meet in the middle.
Static arrays have a fixed size, set when the program is compiled. This makes accessing elements very fast. The downside is that you can't add more elements once the array is full. Doing so causes an error or unexpected behavior. Python hides this from you, but the underlying concepts are still worth knowing.
Unlike static arrays, dynamic arrays can grow and shrink while the program is running. The tradeoff is that the computer has to track the array's size, and when the array outgrows its spot in memory, it has to be copied to a bigger one. That costs more time and memory than a static array. Luckily, smart resizing strategies make this amortized constant time.
class DynamicArray:
def __init__(self):
self.array = [None] # Start with an array of size 1
self.size = 0
def append(self, element):
if self.size == len(self.array):
# Array is full, time to resize
new_array = [None] * (2 * len(self.array))
# Copy elements to the new array
for i in range(self.size):
new_array[i] = self.array[i]
self.array = new_array
print(f"Array doubled. New capacity: {len(self.array)}")
# Add the new element
self.array[self.size] = element
self.size += 1
# Example usage
dynamic_array = DynamicArray()
for i in range(5):
dynamic_array.append(i)
print(f"Added {i}. Current size: {dynamic_array.size}, Capacity: {len(dynamic_array.array)}")
Most languages have dynamic arrays built in. Python calls them List, and Java calls them ArrayList. Get comfortable with whatever your language offers, since you'll use them constantly.
Stacks follow the LIFO principle. They aren't arrays themselves, but they're often built on top of one. Since stacks only add or remove from the end, arrays are a natural fit. They have two main operations: push adds an item on top, and pop removes the top item. Both run in constant time.
| Operations | Time | Space |
|---|---|---|
| Push | O(1) | O(1) |
| Pop | O(1) | O(1) |
| Peek | O(1) | O(1) |
| Is Empty | O(1) | O(1) |
| Size | O(1) | O(1) |
| Clear | O(1) | O(1) |

The animation above shows how stacks work in practice. Just like a stack of plates where you can only add or remove from the top, each operation works with the most recently added element. This LIFO behavior makes stacks ideal when each element depends on the one that came before.
Arrays and stacks come up constantly in coding interviews. The tips below cover the patterns to watch for and the mistakes that trip people up most:
A technique using two pointers to traverse data structures efficiently.
A technique for processing contiguous subarrays or substrings efficiently.
An efficient algorithm for finding elements in sorted data by halving the search space.
A technique for preprocessing arrays to answer range sum queries in constant time.
The patterns above show up everywhere on LeetCode. Work through these classic array and stack problems to practice in-place updates, single-pass scans, and the LIFO patterns you just learned about:
Completed | Title | Status | Notes | Solution | |
|---|---|---|---|---|---|
Easy | |||||
Easy | |||||
Easy | |||||
Easy | |||||
Medium | |||||
Medium | |||||
Medium | |||||
Medium | |||||
Hard | |||||
Hard | |||||
Hard | |||||
Hard |