In this article
  1. Before you start
  2. Why interviewers ask this
  3. KNN search from first principles
  4. The Staff-level answer
  5. The rubric
  6. Three variants of this question
  7. Common mistakes
  8. Follow-up questions
  9. Sources

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 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

A KNN search stores, measures, selects and votesFlow of four steps: labelled points are stored, the distance from the query to every point is measured, the k smallest distances are kept, and a majority vote of their labels gives the answer.Storepointslist ofpairsMeasuredistanceto allKeep ksmallestheapqMajorityvotepredictedlabelDiagram: Interview Clozo, CC BY 4.0 ·interview.clozo.ai/blog/how-would-you-write-a-knn-search-using-base-python.html

A KNN search is four moves: store, measure, select the k nearest, vote.CC BY 4.0 — source: How would you write a KNN search using base Python?
Step-by-step description
  1. Keep the labelled points in a list.
  2. Compute the distance to every point.
  3. Keep the k smallest distances.
  4. 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 label

strict=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

The three nearest points to the query are all redBar chart of the distance from the query (3, 2) to six points: A 2.2361, B 1.4142, C 2.2361, D 3.6056, E 3.6056 and F 5.6569; the three shortest bars are the red points.Distance to query0246ABCDEFStored point (A to C red, D to F blue)2.23611.41422.23613.60563.60565.6569Diagram: Interview Clozo, CC BY 4.0 ·interview.clozo.ai/blog/how-would-you-write-a-knn-search-using-base-python.html
The three shortest bars belong to red points, so the k=3 vote is red.CC BY 4.0 — source: How would you write a KNN search using base Python?
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

Interviewer asks

How would you write a KNN search using base Python?

A strong answer

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

A strong answer selects without a full sort and handles ties, and a Staff answer adds scaling and the point where brute force stops working.
DimensionWeak answerStrong answerStaff answer
CorrectnessLoops, sorts, returns the most common labelRight distance, k slice, voteAlso handles ties and an even k explicitly
DepthNo cost statedStates cost grows with N and DCompares sort, heap and tree indexes
Trade-offsBrute force onlyNotes squared distance saves workSays when to switch to a k-dimensional (KD) tree or ball tree
CommunicationSilent codingNarrates the four stepsStates assumptions, then tests on a tiny example
Follow-up readinessStuck on scalingMentions scalingExplains 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.nsmallest when 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."
  • "zip handles 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 use math.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.

Key takeaways
  • 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.nsmallest instead 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

  1. heapq — Heap queue algorithm — Python Software Foundation, 26 Oct 2026
  2. heapq — Heap queue algorithm (Python 3.1 documentation, nsmallest) — Python Software Foundation, 26 Oct 2026
  3. math — Mathematical functions (math.dist) — Python Software Foundation, 26 Oct 2026
  4. 1.6 Nearest Neighbors, scikit-learn user guide — scikit-learn, 26 Oct 2026
  5. 1.6 Nearest Neighbors, scikit-learn 1.0 user guide (ball tree) — scikit-learn, 26 Oct 2026