Arrays: 10 Practice Problems with Solutions
Ten classic array interview problems with examples, approach, complexity, and clean Python solutions — Two Sum, Best Time to Buy/Sell Stock, Kadane's, Rotate Array, Product Except Self, and more.
What you'll learn
- ✓Ten array problems that appear in real interviews
- ✓How to recognise the underlying pattern for each one
- ✓Clean Python solutions with comments and complexity analysis
- ✓The two-pointer, hash-map, prefix-sum, and Kadane idioms in action
Prerequisites
- •Arrays operations — see Arrays — Common Operations
- •Big-O notation — see Big-O
This post is a workout. Ten classic array problems, each presented with a clear statement, examples, an explained approach, and an annotated Python solution. Try each one yourself for 15-25 minutes before reading the solution — that’s where the learning happens.
The problems are arranged roughly by difficulty and by the techniques they introduce. If you get stuck, scroll to the approach but try to finish the code on your own.
1. Two Sum
Problem. Given an array of integers nums and an integer target, return the indices of the two numbers that add up to target. You may assume exactly one such pair exists, and you may not use the same element twice.
Examples.
nums = [2, 7, 11, 15], target = 9 → [0, 1] (2 + 7 = 9)
nums = [3, 2, 4], target = 6 → [1, 2]
nums = [3, 3], target = 6 → [0, 1]
Approach. The brute force is to try every pair — O(n²). The trick is to walk once and store every value we’ve seen in a hash map keyed by value, with its index as the value. For each new x, check if target - x is in the map. O(n) time, O(n) space.
def two_sum(nums, target):
seen = {} # value -> index
for i, x in enumerate(nums):
complement = target - x
if complement in seen:
return [seen[complement], i]
seen[x] = i
return [] # problem promises a pair exists
print(two_sum([2, 7, 11, 15], 9)) # [0, 1]
This is the single most-asked array problem in interviews. The hash-map-for-lookup pattern shows up everywhere.
2. Best Time to Buy and Sell Stock
Problem. Given an array prices where prices[i] is the price of a stock on day i, return the maximum profit from buying on one day and selling on a later day. Return 0 if no profit is possible.
Examples.
prices = [7, 1, 5, 3, 6, 4] → 5 (buy at 1, sell at 6)
prices = [7, 6, 4, 3, 1] → 0
Approach. Walk left to right tracking the minimum price seen so far. For each day, the best profit ending today is today's price - min so far. Take the max over all days. O(n) time, O(1) space.
def max_profit(prices):
min_so_far = float("inf")
best = 0
for price in prices:
if price < min_so_far:
min_so_far = price
else:
best = max(best, price - min_so_far)
return best
print(max_profit([7, 1, 5, 3, 6, 4])) # 5
print(max_profit([7, 6, 4, 3, 1])) # 0
This is the “running min/max while computing the answer” pattern — closely related to Kadane’s.
3. Move Zeroes
Problem. Given an array nums, move all zeroes to the end while keeping the relative order of the non-zero elements. Do it in place, with no extra array.
Examples.
nums = [0, 1, 0, 3, 12] → [1, 3, 12, 0, 0]
nums = [0] → [0]
Approach. Two-pointer write index. Walk through with a read pointer; whenever you find a non-zero value, write it to the write position and advance write. After the pass, fill the rest of the array with zeroes. O(n) time, O(1) space.
def move_zeroes(nums):
write = 0
for read in range(len(nums)):
if nums[read] != 0:
nums[write] = nums[read]
write += 1
# fill the remaining positions with zero
for i in range(write, len(nums)):
nums[i] = 0
nums = [0, 1, 0, 3, 12]
move_zeroes(nums)
print(nums) # [1, 3, 12, 0, 0]
The two-pointer in-place idiom is one of the most reusable patterns in array DSA.
4. Maximum Subarray (Kadane’s Algorithm)
Problem. Given an integer array nums, find the contiguous subarray with the largest sum and return that sum.
Examples.
nums = [-2, 1, -3, 4, -1, 2, 1, -5, 4] → 6 ([4, -1, 2, 1])
nums = [1] → 1
nums = [-1, -2, -3] → -1
Approach. At each index, the best subarray ending here is either just the current element, or the current element added to the best subarray ending at the previous index. Track the best of those across the array. O(n) time, O(1) space.
def max_subarray(nums):
best_ending_here = nums[0]
best_overall = nums[0]
for x in nums[1:]:
best_ending_here = max(x, best_ending_here + x)
best_overall = max(best_overall, best_ending_here)
return best_overall
print(max_subarray([-2, 1, -3, 4, -1, 2, 1, -5, 4])) # 6
Kadane’s algorithm is the canonical example of dynamic programming on an array with a single variable. It collapses what looks like an O(n²) search into a single linear walk.
5. Rotate Array
Problem. Given an array nums and an integer k, rotate the array to the right by k steps. Do it in place using O(1) extra space.
Examples.
nums = [1, 2, 3, 4, 5, 6, 7], k = 3 → [5, 6, 7, 1, 2, 3, 4]
nums = [-1, -100, 3, 99], k = 2 → [3, 99, -1, -100]
Approach. The clever in-place trick: reverse the whole array, then reverse the first k elements, then reverse the rest. Each reversal is O(n) but uses O(1) space. Total: O(n) time, O(1) space.
def rotate(nums, k):
n = len(nums)
k %= n # handle k > n
def reverse(lo, hi):
while lo < hi:
nums[lo], nums[hi] = nums[hi], nums[lo]
lo += 1
hi -= 1
reverse(0, n - 1)
reverse(0, k - 1)
reverse(k, n - 1)
nums = [1, 2, 3, 4, 5, 6, 7]
rotate(nums, 3)
print(nums) # [5, 6, 7, 1, 2, 3, 4]
If you can derive the three-reversal trick on paper for [1, 2, 3, 4, 5] with k=2, you understand it. Walk through it once and the pattern sticks.
6. Contains Duplicate
Problem. Given an integer array nums, return True if any value appears at least twice, and False if every element is distinct.
Examples.
nums = [1, 2, 3, 1] → True
nums = [1, 2, 3, 4] → False
nums = [1, 1, 1, 3, 3, 4, 3, 2, 4, 2] → True
Approach. Brute force O(n²) — try every pair. Sorting + scan adjacent pairs — O(n log n). Hash set — O(n) time, O(n) space, and the cleanest code.
def contains_duplicate(nums):
seen = set()
for x in nums:
if x in seen:
return True
seen.add(x)
return False
print(contains_duplicate([1, 2, 3, 1])) # True
print(contains_duplicate([1, 2, 3, 4])) # False
Even shorter, with a one-liner trick:
def contains_duplicate_short(nums):
return len(set(nums)) < len(nums)
Both are O(n). Use whichever you find clearer.
7. Single Number
Problem. Given a non-empty array of integers where every element appears twice except for one, find that single one. Solve it in O(n) time and O(1) extra space.
Examples.
nums = [2, 2, 1] → 1
nums = [4, 1, 2, 1, 2] → 4
nums = [1] → 1
Approach. Hash maps work but use O(n) space. The elegant solution exploits XOR:
x ^ x = 0(anything XOR’d with itself is zero)x ^ 0 = x(XOR with zero leaves x unchanged)- XOR is commutative and associative
So XOR-ing every element together cancels out the pairs and leaves the singleton. O(n) time, O(1) space.
def single_number(nums):
result = 0
for x in nums:
result ^= x
return result
print(single_number([4, 1, 2, 1, 2])) # 4
This is a classic example of a bitwise trick replacing an entire data structure. Worth memorising.
8. Plus One
Problem. Given a non-empty array of decimal digits representing a non-negative integer (most-significant digit first), add one to the integer and return the resulting array of digits. The input has no leading zeroes except for the number 0 itself.
Examples.
digits = [1, 2, 3] → [1, 2, 4]
digits = [4, 3, 2, 1] → [4, 3, 2, 2]
digits = [9] → [1, 0]
digits = [9, 9] → [1, 0, 0]
Approach. Walk from the last digit backwards. If the digit is less than 9, add one and return. Otherwise set it to 0 and continue (carry the one). If the loop finishes (every digit was 9), prepend a 1 to the array. O(n) worst case, O(1) extra space (we modify in place except for the all-nines case).
def plus_one(digits):
for i in range(len(digits) - 1, -1, -1):
if digits[i] < 9:
digits[i] += 1
return digits
digits[i] = 0
# If we reach here, every digit was 9 → result is 1 followed by zeros
return [1] + digits
print(plus_one([1, 2, 3])) # [1, 2, 4]
print(plus_one([9, 9])) # [1, 0, 0]
This is the “carry the one” pattern from grade-school arithmetic. Many “big integer” problems use the same shape.
9. Merge Sorted Array
Problem. Given two sorted integer arrays nums1 and nums2, merge nums2 into nums1 as one sorted array. nums1 has size m + n, where the first m slots hold real values and the last n slots are placeholders (zeroes). nums2 has size n.
Examples.
nums1 = [1, 2, 3, 0, 0, 0], m = 3
nums2 = [2, 5, 6], n = 3
→ nums1 becomes [1, 2, 2, 3, 5, 6]
Approach. The naive method copies nums2 into the back of nums1 and sorts — O((m + n) log (m + n)). The elegant solution walks from the back: place the larger of the two current values at the end of nums1, then move that pointer left. Writing from the back means we never overwrite values we haven’t read yet. O(m + n) time, O(1) space.
def merge(nums1, m, nums2, n):
i = m - 1 # pointer into real values of nums1
j = n - 1 # pointer into nums2
write = m + n - 1 # pointer into back of nums1
while j >= 0:
if i >= 0 and nums1[i] > nums2[j]:
nums1[write] = nums1[i]
i -= 1
else:
nums1[write] = nums2[j]
j -= 1
write -= 1
nums1 = [1, 2, 3, 0, 0, 0]
nums2 = [2, 5, 6]
merge(nums1, 3, nums2, 3)
print(nums1) # [1, 2, 2, 3, 5, 6]
“Write from the back to avoid overwrites” is a useful pattern in any in-place merging or shifting problem.
10. Product of Array Except Self
Problem. Given an integer array nums, return an array answer such that answer[i] equals the product of all the elements of nums except nums[i]. You must solve it without using division and in O(n) time.
Examples.
nums = [1, 2, 3, 4] → [24, 12, 8, 6]
nums = [-1, 1, 0, -3, 3] → [0, 0, 9, 0, 0]
Approach. The trick: for each index i, the answer is (product of everything to the left of i) × (product of everything to the right of i).
We do two passes. First pass: fill the result with prefix products. Second pass: walk from the right, multiplying each entry by a running suffix product. O(n) time. If we don’t count the output array, O(1) extra space.
def product_except_self(nums):
n = len(nums)
result = [1] * n
# First pass: result[i] = product of everything to the LEFT of i
left = 1
for i in range(n):
result[i] = left
left *= nums[i]
# Second pass: multiply by product of everything to the RIGHT of i
right = 1
for i in range(n - 1, -1, -1):
result[i] *= right
right *= nums[i]
return result
print(product_except_self([1, 2, 3, 4])) # [24, 12, 8, 6]
This is one of the most elegant array problems out there. The “left product times right product” insight applies to many range-aggregation problems beyond multiplication.
Try it yourself. Re-implement Product of Array Except Self using an explicit prefix-product array and an explicit suffix-product array (so, two extra arrays plus the result). Confirm it gives the same answer. Then compare your code to the O(1)-extra-space version above and notice how the suffix product folded into a single variable.
How to study these problems
Now that you have ten worked examples, the value comes from revisiting them. A pattern that works:
- Right now. Pick any three problems and re-solve them on a blank page. No peeking. If you get stuck, peek for one line, then close and continue.
- Tomorrow. Re-solve any two you found hardest yesterday.
- In a week. Re-solve all ten in a single session, timing yourself. Aim for 90 minutes total.
- In a month. Try variations:
- Two Sum, but the array is sorted and you must use O(1) space (two pointers).
- Best Time to Buy and Sell Stock, but you can make as many transactions as you want.
- Maximum Subarray, but also return the start and end indices.
- Single Number, but every element appears three times except one.
Each variation forces you to understand the original idea rather than memorise the code.
A pattern checklist
Across these ten problems, the recurring techniques were:
- Hash map for lookup — Two Sum, Contains Duplicate
- Running min/max — Best Time to Buy/Sell, Kadane’s
- Two pointers (read/write) — Move Zeroes
- Two pointers (front/back) — Merge Sorted Array
- Reversal as a building block — Rotate Array
- Bitwise XOR — Single Number
- Carry / chain propagation — Plus One
- Prefix and suffix accumulators — Product Except Self
- Dynamic programming with one variable — Kadane’s
If you recognise the shape of a problem and one of these patterns lights up, you’re 80% of the way to a clean solution.
More practice
Once these ten feel comfortable, try these next:
- Three Sum — find all unique triplets summing to zero. (Sort + two pointers.)
- Container With Most Water — two pointers from the ends, shrink the smaller side.
- Subarray Sum Equals K — prefix sums + hash map.
- Sliding Window Maximum — monotonic deque.
- Trapping Rain Water — left max and right max arrays, or two pointers.
These are direct evolutions of what you’ve learned. With the patterns from this post in hand, you’re ready.
Recap
You now have:
- Clean Python solutions for ten classic array problems
- The complexity analysis behind each one
- Eight reusable patterns: hash map lookup, two pointers (two flavours), running extreme, reversal, prefix/suffix accumulators, bitwise tricks, carry chains, and Kadane-style DP
- A study plan for making these stick across the next month
Next steps
You’ve covered arrays end to end — what they are, how they cost, how to operate on them, and how to solve real problems with them. The next foundational structure to learn is the string — close cousin of the array, with its own bag of tricks.
If you’d like a deeper foundation first, revisit:
Questions or feedback? Email codeloomdevv@gmail.com.