Top Array Interview Questions (with Answers)
30 array questions that come up again and again in placement coding rounds, grouped into five patterns: two pointers, sliding window, prefix sums, hashing, and intervals and sorting. Each has a LeetCode link, the approach, a Python sketch and its complexity.
In this article
Arrays are the first data structure most interviewers reach for. Every language has them, a problem can be stated in two lines, and an online judge can check a solution against hidden test cases automatically. That makes them a natural fit for online assessments and first technical rounds. They also sit underneath much of what comes later: strings are arrays of characters, matrices are arrays of arrays, and most dynamic programming tables are arrays filled in a careful order.
The useful part is that array questions repeat. A handful of ideas solve most of them: moving two indices, keeping a running window, precomputing sums, trading memory for fast lookups, and sorting first. Learning to spot which idea a problem needs is worth more than memorising any single solution, so this sheet is organised by pattern rather than by difficulty.
How to use it:
- Read the pattern first, especially the part about how to recognise it.
- Attempt each problem on LeetCode using the Practice link before you open the answer. Give it a fixed amount of time, not unlimited.
- Check the answer for the approach, a short Python sketch and the time and space complexity. The approaches carry over directly to C++ or Java.
- Tick a question only when you can solve it without looking. Your progress is saved in this browser.
- Read the pattern
- Attempt the problem
- Check the answer
- Re-solve later
Two pointers
Two pointers means keeping two indices into the array and moving them by a rule, so that one pass does the work of a nested loop. The indices either start at opposite ends and walk towards each other, or both start on the left, one reading and one writing. Recognise it when the array is sorted (or can be sorted without losing what the question asks for), when you need a pair or triplet that meets a target, when the answer depends on the two ends of a range, or when you must rearrange elements in place with O(1) extra space. Every correct two-pointer solution rests on one argument: each move discards only positions that cannot be part of the answer.
Two pointers
Show answer
Keep a write pointer
wmarking where the next non-zero value belongs. Scan with a read pointer; whenever you find a non-zero value, swap it into positionwand advancew. Non-zero values keep their relative order, and the zeros end up at the back in a single pass.def move_zeroes(nums): w = 0 for r in range(len(nums)): if nums[r] != 0: nums[w], nums[r] = nums[r], nums[w] w += 1Time: O(n). Space: O(1).
Show answer
Start
lat the first element andrat the last. If the pair sums to less than the target, the only way to increase it is to movelright; if it is too large, moverleft. Because the array is sorted, each move discards an element that cannot pair with anything still in range. The problem expects 1-based indices.def two_sum_sorted(numbers, target): l, r = 0, len(numbers) - 1 while l < r: s = numbers[l] + numbers[r] if s == target: return [l + 1, r + 1] # 1-based indices if s < target: l += 1 else: r -= 1 return []Time: O(n). Space: O(1).
Show answer
This is the Dutch national flag problem. Keep three pointers: everything before
lowis 0, everything afterhighis 2, andmidscans the unknown middle. A 0 is swapped tolow, a 2 is swapped tohigh, and a 1 stays. After swapping withhigh, do not advancemid, because the value that just arrived has not been examined yet.def sort_colors(nums): low, mid, high = 0, 0, len(nums) - 1 while mid <= high: if nums[mid] == 0: nums[low], nums[mid] = nums[mid], nums[low] low += 1 mid += 1 elif nums[mid] == 1: mid += 1 else: nums[mid], nums[high] = nums[high], nums[mid] high -= 1Time: O(n), one pass. Space: O(1).
Show answer
Sort the array. Fix each element
nums[i]in turn and run the Two Sum II technique on the rest of the array, looking for a pair that sums to-nums[i]. Skip repeated values ofnums[i], and after recording a triplet skip repeated values ofnums[l], so no triplet is reported twice. Oncenums[i]is positive, no later triplet can sum to zero.def three_sum(nums): nums.sort() res = [] for i in range(len(nums) - 2): if nums[i] > 0: break if i > 0 and nums[i] == nums[i - 1]: continue l, r = i + 1, len(nums) - 1 while l < r: s = nums[i] + nums[l] + nums[r] if s < 0: l += 1 elif s > 0: r -= 1 else: res.append([nums[i], nums[l], nums[r]]) l += 1 r -= 1 while l < r and nums[l] == nums[l - 1]: l += 1 return resTime: O(n²): the sort is O(n log n) and each of the n fixed elements runs a linear scan. Space: O(1) extra apart from the output and the memory the sort uses.
Show answer
Start with the widest container,
l = 0andr = n - 1. Its area ismin(height[l], height[r]) * (r - l). Then move the shorter line inwards. Moving the taller line can never help: the width shrinks, and the height is still capped by the shorter line, so every container using the shorter line has already been beaten.def max_area(height): l, r = 0, len(height) - 1 best = 0 while l < r: best = max(best, min(height[l], height[r]) * (r - l)) if height[l] < height[r]: l += 1 else: r -= 1 return bestTime: O(n). Space: O(1).
Show answer
The water above bar
iismin(highest bar on its left, highest bar on its right) - height[i]. Walk two pointers inwards while trackingleft_maxandright_max. Wheneverheight[l] < height[r], the right side always has a wall at least as tall asleft_max, so the water atldepends only onleft_max: add it and movel. Otherwise do the same from the right.def trap(height): l, r = 0, len(height) - 1 left_max = right_max = 0 water = 0 while l < r: if height[l] < height[r]: left_max = max(left_max, height[l]) water += left_max - height[l] l += 1 else: right_max = max(right_max, height[r]) water += right_max - height[r] r -= 1 return waterTime: O(n). Space: O(1). A prefix-maximum and suffix-maximum array version is easier to explain first and uses O(n) space.
Sliding window
A sliding window is a contiguous range nums[l..r] that moves across the array while you maintain a running summary of it: a sum, a count or a frequency map. Instead of recomputing every subarray from scratch, you add the element entering on the right and remove the one leaving on the left. Fixed-size windows move both ends together. Variable-size windows grow r on every step and shrink l while the window breaks a condition. Recognise it when the question asks for the longest, shortest or best contiguous subarray, or asks you to count subarrays, under a condition that behaves predictably as the window grows. With non-negative numbers, for example, adding an element never decreases the sum, so once a window is too large, every larger window starting at the same l is too.
Sliding window
Show answer
A fixed window of size
k. Sum the firstkelements, then slide one step at a time: add the element entering on the right and subtract the one leaving on the left. Track the best sum and divide bykonce at the end.def find_max_average(nums, k): window = sum(nums[i] for i in range(k)) best = window for i in range(k, len(nums)): window += nums[i] - nums[i - k] best = max(best, window) return best / kTime: O(n). Space: O(1).
Show answer
For each day, the best trade that sells today buys at the lowest price seen so far. Keep that running minimum and the best profit. You can see it as a window whose left edge jumps forward to every new minimum price.
def max_profit(prices): min_price = float("inf") best = 0 for p in prices: min_price = min(min_price, p) best = max(best, p - min_price) return bestTime: O(n). Space: O(1).
Show answer
All numbers are positive, so a window's sum only grows as it widens. Extend
rand add to the running total. While the total is at least the target, record the window length and shrink from the left, since a shorter window starting later might still qualify.def min_sub_array_len(target, nums): l = 0 total = 0 best = float("inf") for r, x in enumerate(nums): total += x while total >= target: best = min(best, r - l + 1) total -= nums[l] l += 1 return 0 if best == float("inf") else bestTime: O(n). Each element enters and leaves the window at most once, so the inner loop is O(n) across the whole run, not per step. Space: O(1).
Show answer
Rephrase the question: find the longest window containing at most
kzeros, because those are the zeros you would flip. Count zeros as the window grows, and shrink from the left whenever the count goes abovek.def longest_ones(nums, k): l = 0 zeros = 0 best = 0 for r, x in enumerate(nums): if x == 0: zeros += 1 while zeros > k: if nums[l] == 0: zeros -= 1 l += 1 best = max(best, r - l + 1) return bestTime: O(n). Space: O(1).
Show answer
The numbers are positive, so the product grows as the window widens. For each
r, multiply innums[r]and shrink from the left until the product is belowk. Every subarray that ends atrand starts anywhere inl..ris then valid, which addsr - l + 1to the count. Ifkis 1 or less, no product of positive integers can be below it.def num_subarray_product_less_than_k(nums, k): if k <= 1: return 0 prod = 1 l = 0 count = 0 for r, x in enumerate(nums): prod *= x while prod >= k: prod //= nums[l] l += 1 count += r - l + 1 return countTime: O(n). Space: O(1).
Show answer
Keep a deque of indices whose values are in decreasing order. Before adding index
i, pop every index from the back whose value is not larger thannums[i]; those values can never be a window maximum again. Drop the front index once it falls out of the window. The front of the deque is always the current window's maximum.from collections import deque def max_sliding_window(nums, k): dq = deque() # indices; their values decrease from front to back res = [] for i, x in enumerate(nums): while dq and nums[dq[-1]] <= x: dq.pop() dq.append(i) if dq[0] <= i - k: dq.popleft() if i >= k - 1: res.append(nums[dq[0]]) return resTime: O(n), because each index is pushed and popped at most once. Space: O(k) for the deque.
Every variable-size window above depends on that predictable behaviour: either all the numbers are positive, or the condition is a count that only rises as the window grows. When the array can contain negative numbers, shrinking the window no longer reliably fixes a sum that is too large, and the sliding window quietly gives wrong answers. That is the gap prefix sums fill.
Prefix sums
A prefix sum array stores running totals: prefix[i] is the sum of the first i elements, so the sum of any range nums[l..r] is prefix[r + 1] - prefix[l], a single subtraction. Recognise it when you must answer many range-sum queries on an array that does not change, when the question asks about subarrays whose sum equals or is divisible by some value (especially when negative numbers rule out a sliding window), or when you need a balance point between a left part and a right part. The same idea works for running products, counts and XOR. The most common upgrade stores prefix sums in a hash map as you go, so "was there an earlier point where the running total was prefix - k?" becomes one lookup.
Prefix sums
Show answer
Build the prefix array once in the constructor, with a leading 0 so that ranges starting at index 0 need no special case. Each query is then one subtraction.
class NumArray: def __init__(self, nums): self.prefix = [0] for x in nums: self.prefix.append(self.prefix[-1] + x) def sumRange(self, left, right): return self.prefix[right + 1] - self.prefix[left]Time: O(n) to build, O(1) per query. Space: O(n).
Show answer
Compute the total once. Walk left to right keeping the sum of everything before the current index; the sum to the right is then
total - left - nums[i]. Return the first index where the two sides match, or -1.def pivot_index(nums): total = sum(nums) left = 0 for i, x in enumerate(nums): if left == total - left - x: return i left += x return -1Time: O(n). Space: O(1).
Show answer
The sum of a subarray ending at
jisprefix[j + 1]minus some earlier prefix, so the best one subtracts the smallest earlier prefix. Kadane's algorithm is the same idea in running form:curis the best sum of a subarray ending at the current index, and you restart from the current element whenever carrying the previous sum would only drag it down. Start fromnums[0], not 0, so an all-negative array returns its largest element.def max_sub_array(nums): best = cur = nums[0] for i in range(1, len(nums)): cur = max(nums[i], cur + nums[i]) best = max(best, cur) return bestTime: O(n). Space: O(1).
Show answer
The answer at
iis the product of everything beforeitimes the product of everything after it. Fill the output with prefix products in a left-to-right pass, then multiply in suffix products in a right-to-left pass. No division is needed, which also avoids the special cases that zeros would cause.def product_except_self(nums): n = len(nums) ans = [1] * n prefix = 1 for i in range(n): ans[i] = prefix prefix *= nums[i] suffix = 1 for i in range(n - 1, -1, -1): ans[i] *= suffix suffix *= nums[i] return ansTime: O(n). Space: O(1) extra, since the problem does not count the output array.
Show answer
Negative numbers are allowed, so a sliding window will not work. Keep a running prefix sum and a hash map counting how often each prefix sum has occurred. A subarray ending here sums to
kexactly when an earlier prefix equalsprefix - k, so add that count. Seed the map with a prefix sum of 0 seen once, for subarrays that start at index 0.def subarray_sum(nums, k): seen = {0: 1} # prefix sum -> number of times seen prefix = 0 count = 0 for x in nums: prefix += x count += seen.get(prefix - k, 0) seen[prefix] = seen.get(prefix, 0) + 1 return countTime: O(n) on average, because of the hash map. Space: O(n).
Show answer
Treat every 0 as -1. A subarray with equal numbers of 0s and 1s now has a sum of 0, which means the running sum at its end equals the running sum just before its start. Store the first index where each running sum appears; the longest answer ending at
iisiminus that first index. Seed the map with sum 0 at index -1.def find_max_length(nums): first = {0: -1} # running sum -> earliest index prefix = 0 best = 0 for i, x in enumerate(nums): prefix += 1 if x == 1 else -1 if prefix in first: best = max(best, i - first[prefix]) else: first[prefix] = i return bestTime: O(n). Space: O(n).
Hashing
A hash set or hash map gives average O(1) insertion and lookup, which turns "search the rest of the array for something" from an O(n) scan into a single step. Recognise it when your brute force has an inner loop hunting for a complement, a duplicate or a count, when you need frequencies, or when membership matters more than order. The cost is O(n) extra memory, and the O(1) is an average: heavy collisions can make individual operations slower, which is worth knowing if the interviewer asks about worst cases. If you are asked for O(1) extra space, look for a sort-plus-two-pointers solution, or for a way to use the array itself as the hash table, as in First Missing Positive below.
Hashing
Show answer
Store each value's index in a hash map as you scan. For each element
x, check whethertarget - xis already in the map before insertingx; checking first stops an element from pairing with itself.def two_sum(nums, target): index_of = {} for i, x in enumerate(nums): if target - x in index_of: return [index_of[target - x], i] index_of[x] = i return []Time: O(n). Space: O(n). Sorting plus two pointers also finds the pair but loses the original indices unless you sort (value, index) pairs.
Show answer
Add each value to a set and return as soon as you meet one that is already there. If memory is tight, sorting and comparing neighbours works too, at O(n log n) time.
def contains_duplicate(nums): seen = set() for x in nums: if x in seen: return True seen.add(x) return FalseTime: O(n). Space: O(n).
Show answer
Counting with a hash map and returning the most frequent value is the direct answer. The usual follow-up asks for O(1) space: Boyer-Moore voting keeps one candidate and a counter, adding 1 for a match and subtracting 1 otherwise, and takes a new candidate whenever the counter reaches 0. Because the majority value appears more than n/2 times, it survives all the cancelling.
from collections import Counter def majority_element(nums): counts = Counter(nums) return max(counts, key=counts.get) def majority_element_voting(nums): # Boyer-Moore, O(1) space candidate, count = None, 0 for x in nums: if count == 0: candidate = x count += 1 if x == candidate else -1 return candidateTime: O(n) for both. Space: O(n) with the counter, O(1) with voting.
Show answer
Count frequencies with a hash map. No value can occur more than n times, so place each value in a bucket indexed by its frequency, then walk the buckets from highest to lowest until you have
kvalues. A min-heap of sizekis the other common answer, at O(n log k).from collections import Counter def top_k_frequent(nums, k): counts = Counter(nums) buckets = [[] for _ in range(len(nums) + 1)] # buckets[f]: values seen f times for value, freq in counts.items(): buckets[freq].append(value) res = [] for freq in range(len(nums), 0, -1): for value in buckets[freq]: res.append(value) if len(res) == k: return res return resTime: O(n). Space: O(n).
Show answer
Put every value in a set. A value
xstarts a run only ifx - 1is not in the set; from each such start, count upwards whilex + 1,x + 2and so on are present. Every value is visited at most twice overall. Loop over the set rather than the original array, so duplicate starting values do not repeat the same run.def longest_consecutive(nums): values = set(nums) best = 0 for x in values: if x - 1 not in values: # x starts a run length = 1 while x + length in values: length += 1 best = max(best, length) return bestTime: O(n) on average. Space: O(n). Sorting first gives a simpler O(n log n) solution, which is a fine starting point to mention.
Show answer
The answer is always between 1 and n + 1, so only values in
1..nmatter. Use the array as its own hash table: swap each such valuevinto indexv - 1until every position holds its own value or a value that does not belong anywhere. The first indexiwherenums[i] != i + 1gives the answeri + 1; if every position matches, the answer is n + 1.def first_missing_positive(nums): n = len(nums) for i in range(n): while 1 <= nums[i] <= n and nums[nums[i] - 1] != nums[i]: j = nums[i] - 1 nums[i], nums[j] = nums[j], nums[i] for i in range(n): if nums[i] != i + 1: return i + 1 return n + 1Time: O(n). The while loop looks nested, but every swap puts one value in its final place, so there are at most n swaps in total. Space: O(1). Compute
jbefore swapping; in Python, writing the swap withnums[nums[i] - 1]on both sides readsnums[i]after it has already changed.
Intervals and sorting
Sorting costs O(n log n) and often makes the rest of the problem linear: duplicates become neighbours, pairs can be found with two pointers, and overlapping ranges end up next to each other. Interval questions, such as meetings, bookings or time ranges given as [start, end] pairs, almost always begin with a sort. Sort by start when you need to merge overlapping intervals, and by end when you are greedily keeping as many non-overlapping intervals as possible. Then make one pass, comparing each interval with the last one you kept. Recognise the pattern when the input is a list of ranges, when the brute force compares every pair, or when the input order does not affect the answer. The sort usually dominates the final complexity, so say that when you state it.
Intervals and sorting
Show answer
nums1has room for both arrays at its end. Fill it from the back: compare the largest remaining elements of each array and write the larger one into the last free slot. Filling from the back never overwrites anums1value you still need. Whennums2runs out, the rest ofnums1is already in place.def merge(nums1, m, nums2, n): i, j, w = m - 1, n - 1, m + n - 1 while j >= 0: if i >= 0 and nums1[i] > nums2[j]: nums1[w] = nums1[i] i -= 1 else: nums1[w] = nums2[j] j -= 1 w -= 1Time: O(m + n). Space: O(1).
Show answer
Sort by start. Walk through the intervals: if the current one starts at or before the end of the last merged interval, they overlap, so extend that end to the larger of the two; otherwise, start a new merged interval.
def merge_intervals(intervals): intervals.sort(key=lambda iv: iv[0]) merged = [] for start, end in intervals: if merged and start <= merged[-1][1]: merged[-1][1] = max(merged[-1][1], end) else: merged.append([start, end]) return mergedTime: O(n log n) for the sort; the merge pass is O(n). Space: O(n) for the output.
Show answer
The input is already sorted and non-overlapping, so no sort is needed. Work in three phases: copy every interval that ends before the new one starts, merge every interval that starts before the new one ends into it, then copy the rest.
def insert(intervals, new_interval): res = [] i, n = 0, len(intervals) start, end = new_interval while i < n and intervals[i][1] < start: res.append(intervals[i]) i += 1 while i < n and intervals[i][0] <= end: start = min(start, intervals[i][0]) end = max(end, intervals[i][1]) i += 1 res.append([start, end]) res.extend(intervals[i:]) return resTime: O(n). Space: O(n) for the output.
Show answer
Minimising removals is the same as keeping as many non-overlapping intervals as possible. Sort by end time and greedily keep each interval that starts at or after the end of the last one kept: the interval that finishes earliest leaves the most room for the rest. In this problem, intervals that only touch, such as
[1, 2]and[2, 3], do not overlap.def erase_overlap_intervals(intervals): intervals.sort(key=lambda iv: iv[1]) kept = 0 last_end = float("-inf") for start, end in intervals: if start >= last_end: kept += 1 last_end = end return len(intervals) - keptTime: O(n log n). Space: O(1) extra apart from the sort.
Show answer
Both lists are sorted and disjoint, so walk them with one pointer each. Two intervals intersect in
[max(starts), min(ends)]whenever that range is not empty. Then advance whichever interval ends first, because it cannot intersect anything later in the other list.def interval_intersection(first, second): i = j = 0 res = [] while i < len(first) and j < len(second): lo = max(first[i][0], second[j][0]) hi = min(first[i][1], second[j][1]) if lo <= hi: res.append([lo, hi]) if first[i][1] < second[j][1]: i += 1 else: j += 1 return resTime: O(m + n). Space: O(1) extra apart from the output.
Show answer
Sorting and taking
nums[n - k]works in O(n log n), and is worth saying first. The expected improvement keeps a min-heap of theklargest values seen so far: push each value, and pop the smallest whenever the heap grows pastk. The top of the heap is the answer. Quickselect with a random pivot is the usual follow-up, at O(n) on average and O(n²) in the worst case.import heapq def find_kth_largest(nums, k): heap = [] for x in nums: heapq.heappush(heap, x) if len(heap) > k: heapq.heappop(heap) return heap[0]Time: O(n log k). Space: O(k).
Real interview problems often combine these patterns. 3Sum is sorting plus two pointers, Subarray Sum Equals K is prefix sums plus hashing, and Sliding Window Maximum adds a deque to a window. When a new problem does not fit one pattern cleanly, ask which two it might combine.
How to practise this sheet
Solving 30 problems once is not the goal. The goal is solving a problem you have never seen, from one of these patterns, under time pressure. Three habits make the difference.
Spaced repetition
Re-solve each problem from a blank editor after a gap; re-reading the answer does not count. A simple schedule is to attempt a problem today, again a few days later, and again a week or two after that. If you get stuck on a revisit, shorten the gap for that problem. Keep a one-line note for each problem with its pattern and key insight, for example "shrink while the window holds more than k zeros". Those notes become your revision sheet before interviews.
Time yourself
Set a timer before you start. A reasonable starting target is about 15 to 20 minutes for an easy problem and 30 to 40 for a medium, including reading the problem and testing your code. If you have made no progress after about 20 minutes, read only the approach paragraph of the answer, close it, and write the code yourself. Online assessments are timed, so practise under the same kind of pressure.
Explain aloud
Interviewers assess your reasoning, not only your final code. Practise saying your solution out loud in this order:
- Restate the problem and ask about edge cases: empty input, a single element, duplicates, negative numbers, very large values.
- Describe the brute force and its complexity.
- Name the pattern that improves it and explain why the improvement is correct.
- Write the code, narrating the important decisions.
- Dry-run it on a small example, then state the time and space complexity.
Recording yourself once or twice is uncomfortable, and it shows you quickly where your explanation gets vague.
Arrays are one part of a wider plan. The 90-day placement preparation roadmap shows where this sheet fits alongside the other DSA topics, CS fundamentals, aptitude and mock interviews.
Keep learning
Free 90-Day Placement Preparation Roadmap
A 90-day plan for campus placements in seven stages: DSA foundations, core patterns, CS fundamentals, aptitude, projects and resume, mock interviews and final revision. Each stage says what to do and how to tell when you are ready to move on.
RoadmapWebAll levels
FreeView roadmapFree Quantitative Aptitude Formula Sheet (Free PDF)
Every formula from our quantitative aptitude guide on a printable PDF: percentages, interest, ratios, time and work, speed and distance, permutations and probability.
Free PDFPDFBeginner
FreeDownloadFree 20 HR Interview Questions for Freshers (with Sample Answers)
The HR questions freshers commonly face in campus and off-campus interviews, grouped by theme. For each one: what the interviewer is checking, a simple structure for your answer, and a short sample answer to adapt to your own experience.
GuideWebBeginner
FreeReadFree Quantitative Aptitude Formulas for Placement Tests
A formula sheet for the quantitative section of placement aptitude tests, from percentages and interest to time and work, speed, permutations, probability and number system, with a worked example for each topic.
GuideWebBeginner
FreeRead