Understand
30-second explanation
Build the interview-ready mental model before comparing implementations.
Rank points by squared distance x²+y² (skip the square root — it's monotonic and only adds floating-point error). The interview-grade answer is a max-heap of size k: push each point, pop when size exceeds k, and the k smallest distances survive — O(n log k) time, O(k) space. For O(n)-average time, mention quickselect to partition the k closest without ordering them.
Interview contextWhat is testedWhere candidates slipHow to stand outExplore interview contextHide interview context
What's being tested
Whether you drop the square root (monotonicity insight), and whether you select with a size-k heap instead of sorting everything — plus awareness of quickselect for O(n) average.
Where candidates slip
Computing Math.sqrt for the comparison (slower and floating-point fragile), or using a min-heap of all n points and popping k — O(n log n) and O(n) memory.
How to stand out
Say 'compare squared distances' before writing code, give the heap O(n log k) solution, then offer quickselect O(n) average and discuss its worst case and why output order is free.
Choose
Compare the available approaches
Use constraints and trade-offs to select the route you can defend in an interview.
Interactive decision treeInteractiveChoose the right approachFirst decision previewk closest points to originNeed O(n) average?YesNoExplore the full treeCollapse the tree
Learn & trace
Build each solution step by step
Move from the core idea to a visual trace, implementation, proof, and edge cases.
Approach 1 of 2
Max-Heap of Size K
1Hint 1Explore hintHide hint
You need the k smallest by a computed key. To keep 'the k best so far' while streaming, which heap orientation lets you evict the current worst?
2Hint 2Explore hintHide hint
Use a MAX-heap keyed by squared distance, capped at size k. The top is always the farthest of the k you're holding.
3Hint 3Explore hintHide hint
Push each point; if the heap exceeds k, pop the top (the farthest). After all points, the heap holds exactly the k closest.
A · Core idea
Treat it as Top-K with the key being squared distance.
Why squared distance. Euclidean distance is √(x²+y²). Since √ is strictly increasing, comparing x²+y² gives the same ordering as comparing the true distances — but with integer arithmetic, no precision loss, and no sqrt calls. Always state this; it's the cheapest way to look sharp.
Why a max-heap of size k. We want the k smallest distances. Counter-intuitively, keep a max-heap (farthest on top) capped at k. When a new point arrives and the heap is full, compare it implicitly: push it, then pop the top. The top is the farthest among the k+1 candidates, so popping it always discards the least deserving point. Whatever survives all n points is the k closest.
Why not a min-heap of everything. Heapifying all n points into a min-heap and popping k times is O(n + k log n) and stores all n. The size-k max-heap caps memory at k and each operation at log k — better when k ≪ n, and it generalises to streams where n is unbounded.
Python detail. heapq is a min-heap, so push (-distance, x, y). The most-negative (largest true distance) sits on top and gets popped first — exactly the max-heap behaviour we want.
B · Visual dry run
Interactive dry run
Select a frame to connect the decision with its visual state.
points = [[3,3],[5,-1],[-2,4]], k = 2Array state
compute
distances 18, 26, 20
[[3,3], [-2,4]]C · Code with teaching comments
Reference implementation previewpublic int[][] kClosest(int[][] points, int k) { // Max-heap by squared distance (largest distance on top). PriorityQueue<int[]> heap = new PriorityQueue<>((a, b) -> dist(b) - dist(a)); for (int[] p : points) {Explore the full codeHide the full code
D · Why it works and complexityO(n log k) timeO(k) spaceOpen the invariant and proofExplore the proofHide the proof
The size-k max-heap is a bouncer keeping only the k closest in the club: the bouncer (heap top) is whoever is currently farthest, so any closer newcomer bumps them. You never compare points directly — the heap's ordering decides every eviction.
ComplexityO(n log k) time, O(k) space — Each of n points triggers a push and possibly a pop, each O(log k) because the heap never exceeds k. Total O(n log k). The heap holds at most k entries → O(k) space.
E · Edge cases and pitfalls6points = [[0,0]], k = 1 — Distance 0; single point returned. Origin itself is closest to origin.Explore edge casesHide edge cases
Edge cases handled
points = [[0,0]], k = 1Distance 0; single point returned. Origin itself is closest to origin.k == points.lengthHeap never exceeds k after all pushes; every point is returned.ties like [[1,0],[0,1]], k = 1Both distance 1; either is a valid answer — problem guarantees a unique set on real inputs.
Pitfalls
- Using Math.sqrt for comparison — slower and risks floating-point ties; compare squared distances.
- Building a min-heap of all n points (O(n log n), O(n) memory) instead of a size-k max-heap.
- Reversing the comparator into a min-heap of size k — you'd keep the k FARTHEST points.
Approach 2 of 2 · Optimal
Quickselect (Optimal Average O(n))
1Hint 1Explore hintHide hint
You don't need the k closest sorted — you only need them grouped to the front. What algorithm partitions an array around a pivot so one side is 'smaller'?
2Hint 2Explore hintHide hint
Use quickselect (the selection cousin of quicksort): partition by squared distance so all distances ≤ pivot are on the left.
3Hint 3Explore hintHide hint
Recurse only into the side containing the boundary index k. Once the pivot lands at index k, the first k elements are the answer — in any order.
A · Core idea
Quickselect finds the k smallest without fully sorting.
Partition like quicksort, recurse like binary search. Pick a pivot point, then rearrange so every point with squared distance ≤ the pivot's sits left of it and every farther point sits right. The pivot is now at its final sorted index p.
Recurse into one side only. If p == k, the first k slots hold exactly the k closest — done. If p > k, the answer is entirely within the left part, so recurse there. If p < k, the left part is fully in the answer; recurse right for the remaining k − p. Unlike quicksort, we discard the other half each time, which is why the expected cost collapses to linear.
Why it can beat the heap. No log k factor and no auxiliary heap — it rearranges in place. The cost is a worst case of O(n²) on adversarial pivots, mitigated by random pivot selection. Mention this trade: heap is O(n log k) guaranteed, quickselect is O(n) average but O(n²) worst.
Output order is free. Because we only need the k closest as a set, leaving them unsorted on the left is perfectly valid — that's what makes quickselect applicable here at all.
B · Visual dry run
Interactive dry run
Select a frame to connect the decision with its visual state.
points = [[3,3],[5,-1],[-2,4]], k = 2Interactive diagram
Picking the right approach
pivot = last (d=20)Always compare squared distance. Then choose by k-vs-n and whether O(n) average is needed.
[[3,3], [-2,4]]C · Code with teaching comments
D · Why it works and complexityO(n) average, O(n²) worst timeO(1) extra (in place) spaceOpen the invariant and proofExplore the proofHide the proof
Sorting answers 'what is the full order?' but the question only asks 'which k are smallest?'. Quickselect exploits that weaker requirement: it stops the instant the boundary is placed, never paying for order it doesn't need — the canonical 'selection, not sorting' optimization.
ComplexityO(n) average, O(n²) worst time, O(1) extra (in place) space — Each partition is O(n) and quickselect recurses into only one side, so expected work is n + n/2 + n/4 + … = O(n). A pathological pivot sequence degrades to O(n²); randomising the pivot makes that astronomically unlikely.
E · Edge cases and pitfalls6k == 1 — Quickselect drives the single minimum to index 0; return that one point.Explore edge casesHide edge cases
Edge cases handled
k == 1Quickselect drives the single minimum to index 0; return that one point.all equal distancesEvery comparison is ≤ pivot; partition still terminates and any k are valid.k == nLoop exits immediately (boundary trivially satisfied); return all points.
Pitfalls
- Recursing into BOTH sides — that's quicksort (O(n log n)), defeating the point of quickselect.
- Off-by-one on the boundary (using p vs p+1) — return the wrong count of points.
- Not randomising the pivot — sorted/adversarial input degrades to O(n²).
Practice
Try the solution yourself
Edit the optimal implementation, test another input, and observe the result.
Editable code previewpublic int[][] kClosest(int[][] points, int k) { int lo = 0, hi = points.length - 1; while (lo < hi) { int p = partition(points, lo, hi); if (p == k) break;Explore the playgroundClose the playground
Avoid mistakes
Recognize the interview traps
Understand why common attempts fail before reviewing the corrected implementation.
1Taking the square root unnecessarily
sqrt is monotonic, so it never changes the ordering — it only adds cost and floating-point error that can mis-order near-ties.
Explore the code comparisonHide the code comparison
Taking the square root unnecessarily
sqrt is monotonic, so it never changes the ordering — it only adds cost and floating-point error that can mis-order near-ties.
Don't write this
import math
d = math.sqrt(x * x + y * y) # float, slower, can mis-tie
heapq.heappush(heap, (-d, x, y))Write this instead
d = x * x + y * y # integer squared distance, exact ordering
heapq.heappush(heap, (-d, x, y))2Min-heap of all points instead of size-k max-heap
Heapifying all n and popping k is O(n + k log n) and holds every point. A size-k max-heap is O(n log k) and O(k) memory — strictly better when k ≪ n.
Explore the code comparisonHide the code comparison
Min-heap of all points instead of size-k max-heap
Heapifying all n and popping k is O(n + k log n) and holds every point. A size-k max-heap is O(n log k) and O(k) memory — strictly better when k ≪ n.
Don't write this
PriorityQueue<int[]> heap =
new PriorityQueue<>((a, b) -> dist(a) - dist(b)); // min-heap of all n
for (int[] p : points) heap.offer(p);
int[][] res = new int[k][2];
for (int i = 0; i < k; i++) res[i] = heap.poll();Write this instead
PriorityQueue<int[]> heap =
new PriorityQueue<>((a, b) -> dist(b) - dist(a)); // max-heap, size k
for (int[] p : points) {
heap.offer(p);
if (heap.size() > k) heap.poll();
}Revise
Carry the pattern into the interview
Pattern to remember
Size-k max-heap on squared distance (or quickselect)
Code pattern to memorize
heap = [] # max-heap of (-dist, point)
for (x, y) in points:
d = x*x + y*y
heappush(heap, (-d, x, y))
if len(heap) > k: heappop(heap)
return [[x, y] for d, x, y in heap]Rules to remember
- 1Compare squared distance
x²+y², neversqrt— sqrt is monotonic so ordering is identical, and it avoids float error and cost. - 2Use a max-heap of size k (largest distance on top) so the farthest of the current k is evicted when a closer point arrives.
- 3Python's min-heap → negate the distance to get a max-heap.
- 4The output order is unspecified — don't waste time sorting the final k unless asked.
- 5If the interviewer wants O(n) average, switch to quickselect (partition around the k-th distance).
Use this pattern when
- 'K closest / nearest / smallest by a computed key' problems
- You need the k best by a metric without a full sort
- Streaming points where k stays small relative to n
- Any nearest-neighbour-style selection with a cheap distance proxy
Choose another pattern when
- k ≈ n → just sort all points by distance
- You need them sorted by distance → sort directly, O(n log n)
- k = 1 → linear scan for the single minimum
- Need exact Euclidean distances for output → only then take sqrt at the end
Interview takeaway
K Closest is Top-K wearing a geometry costume: the only twist is the key (squared distance). A size-k max-heap gives O(n log k); quickselect gives O(n) average. Recognise it as the same bounded-heap pattern as Top K Frequent and Kth Largest.
Continue
Transfer the pattern to another problem
Use a direct variation or pattern sibling to prove the idea is reusable.
Transferable patternTop-K selection by a computed key (squared distance). Size-k max-heap is O(n log k); quickselect is O(n) average. Same family as Kth Largest Element and Top K Frequent Elements — recognise the 'k best by a key' shape.
Practice queueDirect variations and pattern siblings
Direct follow-ups
- Kth Largest Element in an ArrayComing soonSame size-k heap or quickselect, keyed by value rather than distance.
- Top K Frequent ElementsIdentical bounded-heap pattern; the key is frequency instead of distance.
- Find K Closest ElementsComing soonOn a sorted array — binary search the window start, then take k contiguous elements.
- Last Stone WeightComing soonA pure max-heap warm-up: repeatedly smash the two heaviest stones.