Skip to content
Reliable Data Engineering
Practice problem medium heaptop-kgeometry
Practise with timer, notes and rubric

K Closest Points to the Origin (Nearest Depots)

Pattern: Heap (Top-K and Greedy Merging) · Difficulty: Medium · Asked at: Amazon, Meta, Uber, DoorDash

Classic version: LeetCode 973

Problem

Given points (x, y) (e.g. delivery depots relative to a customer at the origin), return the k closest by Euclidean distance, sorted by distance ascending; break ties by x, then y.

Examples

k_closest([(1, 3), (-2, 2)], 1)          → [(-2, 2)]
k_closest([(3, 3), (5, -1), (-2, 4)], 2) → [(3, 3), (-2, 4)]

Starter code

def k_closest(points: list[tuple[int, int]], k: int) -> list[tuple[int, int]]:
    pass

Hints

Hint 1

Compare squared distances x² + y²: no sqrt needed, no floating-point error.

Hint 2

Keep a max-heap of size k (push negated keys in Python). When a closer point arrives, evict the farthest.

Where this shows up in data engineering

Nearest-neighbour selection is everywhere: closest drivers in ride-hailing, nearest warehouses, and the top-k step of vector search in RAG systems (an ANN index finds candidates, then an exact top-k heap ranks them). Avoiding sqrt is the same idea as comparing squared L2 or using inner product in vector DBs.

Solution

import heapq


def k_closest(points, k):
    heap = []                                    # max-heap via negated keys
    for x, y in points:
        key = (-(x * x + y * y), -x, -y)         # farthest (and largest tie-breakers) at the root
        if len(heap) < k:
            heapq.heappush(heap, (key, (x, y)))
        elif key > heap[0][0]:                   # closer than the current farthest
            heapq.heapreplace(heap, (key, (x, y)))
    return [p for _, p in sorted(heap, key=lambda e: (-e[0][0], -e[0][1], -e[0][2]))]

Tests

Your solution should pass these:

assert k_closest([(1, 3), (-2, 2)], 1) == [(-2, 2)]
assert k_closest([(3, 3), (5, -1), (-2, 4)], 2) == [(3, 3), (-2, 4)]
assert k_closest([(0, 1), (1, 0), (0, -1), (-1, 0)], 2) == [(-1, 0), (0, -1)]
assert k_closest([(2, 2)], 1) == [(2, 2)]
assert k_closest([(1, 1), (2, 2), (3, 3)], 3) == [(1, 1), (2, 2), (3, 3)]

Explanation

Bounded max-heap: the root is the farthest of the k kept points; any closer newcomer replaces it. O(n log k) time, O(k) space, streaming-friendly.

Tie-breaking: encoding (distance, x, y) into the heap key makes the result deterministic, which matters for reproducible tests and pipelines (non-deterministic top-k is a classic source of flaky reports).

Alternatives: sort all points, O(n log n); quickselect on squared distance, O(n) average but returns the k in arbitrary order (sort them after: O(k log k)).

Follow-up questions

Millions of depots, many queries per second. What changes?

Precompute a spatial index (geohash/H3 cells, k-d tree, or R-tree): look up the query’s cell and its neighbours to get candidates, then run the exact heap over the candidates only. That’s how geo services and ANN vector indexes avoid scanning everything.