In this article
Before you start
This question belongs to the machine learning (ML) coding round guide, where from-scratch prompts like this one are collected.
Why interviewers ask this
A KNN search is a small problem that shows whether a candidate can turn an idea into correct code without a library to lean on. The loop is short, so the interviewer's attention goes to the decisions around it: which distance, whether to sort everything, what happens on a tie, and what happens when one feature is measured in thousands and another in single digits.
A weak answer writes a nested loop, sorts all the distances and returns the most common label, with no remark on cost or scaling. A strong answer selects the k nearest without a full sort, handles ties on purpose and says what its cost is. A Staff-level answer also says when brute force stops being acceptable and which index replaces it. In a design or coding round, a prompt like this one tests that range of judgement as much as the syntax. Beam search uses the same keep-the-best-k idea on partial sequences, so the selection step here transfers.
KNN search from first principles
- Brute-force nearest-neighbour search
- Brute-force nearest-neighbour search is a method that measures the distance from a query to every stored point and then picks the smallest, so it needs no index and no training step but touches every point on every query.
In plain words
Imagine asking the five houses nearest to yours what colour they are painted. Finding those houses is a KNN search. Guessing your own colour from the most common answer is the classifier built on top. The analogy breaks in one place: houses sit on a flat map. Data points can have many features, and "near" then depends on how each feature is measured.
In one precise sentence: given a query, a KNN search finds the k stored points with the smallest distance to it, and a classifier returns the majority label among them. Brute force builds nothing before the first query; the tree variants build an index first, as the scikit-learn guide describes.
The worked example uses six labelled points in two dimensions (illustrative): A (1, 1), B (2, 1) and C (1, 3) are red, D (5, 5), E (6, 4) and F (7, 6) are blue. The query is (3, 2).1
Step 1: Store the labelled points
Step 1 stores the training data as a list of (features, label) pairs. Nothing is computed yet: brute-force KNN has no training phase, and "fitting" it means keeping the list. Input: the labelled points. Output: the same list, kept in memory.
Write a point as a tuple, for example ((1, 1), "red"). A tuple suits a point because it cannot be changed by accident while you loop over it. Keep every point the same length, because a distance is only defined between points of the same dimension, as the math.dist documentation states.
You now know what the search runs over: a plain list. Its size is called N, and the number of features per point is called D. Both appear in the cost at the end of the page.
Step 2: Measure the distance to every stored point
Step 2 computes the distance from the query to each stored point, which costs work proportional to N times D per query: 200,000 coordinate differences for N of 100,000 and D of 2 (illustrative).1 In base Python, math.dist(p, q) returns the Euclidean distance and has existed since Python 3.8; sum((a - b) ** 2 for a, b in zip(p, q, strict=True)) needs no import.
Squared distance gives the same ordering as the true distance, because the square root never swaps two values, so skip the root. For the example, the true distances are 2.2361 (A), 1.4142 (B) and 2.2361 (C). D and E tie at 3.6056 and F is farthest at 5.6569.1
Now the slip from the lead. Take a query (30, 50,000) for an age and an income, with three stored points: red (30, 58,000), blue (60, 50,500) and blue (45, 70,000) (illustrative). Raw squared distances to the first two are 64,000,000 for red and 250,900 for blue, so blue looks nearest only because income is measured in thousands. Fit min-max scaling on the stored points only (age 30 to 60, income 50,500 to 70,000), then apply the same scaling to the query: red is at about 0.4103 and blue at 1.0003, so red wins.1 Scale features before measuring.
Step 3: Keep only the k smallest distances
Step 3 selects the k nearest points without sorting all N distances. The simple route is sorted(...)[:k]; the alternative is heapq.nsmallest(k, train, key=...), which the Python 3.1 documentation describes as equivalent to sorted(iterable, key=key)[:n] while returning only the n smallest. The costs differ. Measuring all distances is O(N·D), a full sort adds O(N log N), and nsmallest adds O(N log k) time with O(k) extra memory. The same Python 3.1 heapq page adds a caveat: these functions "perform best for smaller values of n", sorted() is more efficient for larger values, and min() is better when only one item is needed.
A heap is a binary tree stored in a list in which every parent is smaller than or equal to its children, so the smallest element is always at heap[0] (Python heapq documentation). To keep the k smallest, hold a heap of size k holding the k best so far with the worst at the root; Python's heap is a min-heap, so store negated distances (the max-heap helpers exist only from Python 3.14). A new point enters only if it beats the root.
For 100,000 random distances and k of 5, the heap changed only 50 times, against about 54 expected for randomly ordered data, and the result matched the sorted answer.1 Most points are rejected after one comparison.
On the example, the order is B, A, C, then D and E tied, then F. The three nearest, B, A and C, are all red.1
Step-by-step description
- Keep the labelled points in a list.
- Compute the distance to every point.
- Keep the k smallest distances.
- The most common label wins.
- The labelled points are stored in a list.
- The distance from the query to every point is measured.
- The k smallest distances are kept.
- A majority vote of their labels gives the answer.
Step 4: Take a majority vote of the k labels
Step 4 turns the k nearest labels into one answer. A majority vote is the rule that the label carried by the most neighbours wins; in base Python, collections.Counter counts the labels and the largest count decides.
With k of 3 the example votes red three to zero. With k of 4 the fourth neighbour is blue, one of two tied points D and E at the same distance, and the vote is three to one for red. With k of 5 it is three to two.1 Tied distances at the boundary of k mean "the k nearest" is not unique, so state a rule.
An even k can also split two classes evenly. Pick an odd k for two classes, or break the tie by the closest neighbour, which the code below does. This is the point where a candidate shows the problem was understood.
The whole function in base Python
The code below has two functions. knn_search returns the k nearest (features, label) pairs, and knn_classify votes on them. It uses squared distance, heapq.nsmallest and a tie-break that prefers the label of the closest neighbour, because nsmallest returns its items in ascending order.
import heapq
from collections import Counter
def sq_dist(a, b):
# squared Euclidean distance; same order as the true distance
return sum((x - y) ** 2 for x, y in zip(a, b, strict=True))
def knn_search(train, query, k):
# train: list of (features_tuple, label); features already scaled
return heapq.nsmallest(k, train, key=lambda item: sq_dist(item[0], query))
def knn_classify(train, query, k):
nearest = knn_search(train, query, k)
votes = Counter(label for _, label in nearest)
top = max(votes.values())
for _, label in nearest: # nearest first, so ties go to the closest neighbour
if votes[label] == top:
return labelstrict=True makes zip raise an error if the query and a stored point have different lengths, instead of silently ignoring the extra coordinates.
Calling knn_classify on the six example points with the query (3, 2) and k of 3 returns "red", matching the hand result above.1
Text description of the diagram
- B is nearest at 1.4142.
- A and C tie at 2.2361.
- D and E tie at 3.6056 and F is farthest at 5.6569.
Knowing it is half the job. Saying it is the other half.
Interview Clozo is an AI interview copilot for real interview rounds: it hears the question and puts a strong answer on your screen, and you say it in your own words. See the features or how it works.
Start Free Trial →The Staff-level answer
How would you write a KNN search using base Python?
I'd keep the training data as a list of feature-and-label pairs, since plain KNN has no training step. For a query I compute the squared Euclidean distance to every point, because the square root doesn't change the order, and I keep the k smallest with heapq.nsmallest, so I never sort all N distances. Then I take a majority vote and break ties by the closest neighbour. The cost per query is O(N·D + N log k), where D is the number of features. Before any of that I scale the features so one large unit doesn't dominate. If N grows large I'd move to a k-dimensional (KD) tree or ball tree; for small data, brute force is competitive.
What makes it Staff-level: it names the cost, handles ties and scaling on purpose, and says when the approach stops being right.
The rubric
| Dimension | Weak answer | Strong answer | Staff answer |
|---|---|---|---|
| Correctness | Loops, sorts, returns the most common label | Right distance, k slice, vote | Also handles ties and an even k explicitly |
| Depth | No cost stated | States cost grows with N and D | Compares sort, heap and tree indexes |
| Trade-offs | Brute force only | Notes squared distance saves work | Says when to switch to a k-dimensional (KD) tree or ball tree |
| Communication | Silent coding | Narrates the four steps | States assumptions, then tests on a tiny example |
| Follow-up readiness | Stuck on scaling | Mentions scaling | Explains why unscaled units change the neighbours |
Three variants of this question
How would you write KNN regression in base Python?
Keep the same distance and selection steps, then return the average of the k neighbours' numeric labels instead of a vote. sum(labels) / k is enough; weighting by closeness is optional.
How would you handle data too large to hold in memory?
Read the points one at a time, for example from a generator, and keep a heap of size k, so memory stays at k points whatever N is. Selection with a heap works in one pass, which a full sort cannot do.
How would you weight closer neighbours more?
Give each neighbour's vote a weight such as one divided by its distance, add the weights per label and return the label with the largest total. Guard against a zero distance when the query equals a stored point.
Common mistakes
- "I sort all the distances and slice." (wrong) Sorting is correct, but it costs O(N log N) when k is much smaller than N. (right) "I use
heapq.nsmallestwhen k is small, so only k items are kept." - "I use the raw features." (wrong) A feature in thousands dominates one in single digits. (right) "I scale each feature first, using the same scaling for the query."
- "
ziphandles any two points." (wrong) It silently stops at the shorter point, so a wrong dimension gives a wrong answer without an error. (right) "I check the lengths match, or usemath.dist, which requires the same dimension." - "Ties don't matter." (wrong) An even k can split evenly, and distances can tie at the boundary. (right) "I use an odd k for two classes or break ties by the closest neighbour."
- "A tree is always faster." (wrong) The scikit-learn guide says brute-force searches can be very competitive for small data samples. (right) "For small N brute force is fine; I switch when N grows."
Follow-up questions
How do you find the k nearest neighbours in Python without NumPy?
Compute each distance with math.dist or a sum of squares, then pass the list to heapq.nsmallest(k, ...) with a key. Everything comes from the standard library.
Why use squared distance instead of Euclidean distance in KNN?
The square root never changes which of two distances is smaller, so the ordering and the neighbours are identical. Skipping it saves one operation per point.
Why use a heap instead of sorting all the distances?
A heap holds only the k best so far, so a new point is rejected after one comparison with the root most of the time. The heap changed 50 times over 100,000 random distances in the worked check.1 A full sort orders all N values even though only k matter.
How do you choose k?
Treat k as a setting to test: try several values on held-out labelled data and keep the one that scores best (see evaluation metrics from scratch). A very small k follows noise, and a very large one blurs the classes; for two classes an odd k avoids even splits.
How would you speed up KNN when the data set is large?
Build an index. A k-dimensional (KD) tree is a tree that splits the space along feature axes; per the scikit-learn guide, a built tree answers a query with about log N distance computations, but it loses its edge as dimension grows, where a ball tree can do better. See also the ML coding round guide for neighbouring from-scratch prompts such as attention, softmax, convolution and decision trees and naive Bayes.
- A KNN search measures the distance to every stored point and returns the k nearest; a classifier then votes on their labels.
- Use squared distance and
heapq.nsmallestinstead of a full sort. - Scale features first, or the biggest unit decides the neighbours.
- Decide ties on purpose: an odd k for two classes, or the closest neighbour wins.
- Brute force costs work proportional to N times D per query; trees help when N grows.
Sources
- heapq — Heap queue algorithm — Python Software Foundation, 26 Oct 2026
- heapq — Heap queue algorithm (Python 3.1 documentation, nsmallest) — Python Software Foundation, 26 Oct 2026
- math — Mathematical functions (math.dist) — Python Software Foundation, 26 Oct 2026
- 1.6 Nearest Neighbors, scikit-learn user guide — scikit-learn, 26 Oct 2026
- 1.6 Nearest Neighbors, scikit-learn 1.0 user guide (ball tree) — scikit-learn, 26 Oct 2026