the full guide

K-Nearest Neighbours (KNN): the complete guide

How KNN works, a full worked example by hand, choosing k, distance metrics, scaling, code from scratch and with scikit-learn, and interview questions.

18 min Beginner friendly

What you need: Basic Python helps for the code. The ideas need only addition and multiplication.

What is KNN?

K-Nearest Neighbours (KNN) predicts the answer for something new by looking at the k most similar examples it has already seen. For a category, those neighbours vote. For a number, their values are averaged.

KNN is a supervised learning algorithm, because the examples come with correct answers. It is also called a lazy learner: it does no work during training except storing the data. All the calculating happens when you ask for a prediction.

How it works, in four steps

  1. Choose k, the number of neighbours to ask.
  2. Measure the distance from the new point to every stored example.
  3. Keep the k closest examples.
  4. Take a majority vote for a category, or the average for a number.

A full example, worked by hand

Seven students are recorded by hours studied and hours slept, and whether they passed. A new student studied 5 hours and slept 4. Will they pass?

distance = √[ (study₁ − study₂)² + (sleep₁ − sleep₂)² ]

Student D:  √[ (5 − 6)² + (4 − 5)² ] = √[ 1 + 1 ] = √2 = 1.41

Do the same for every student, then sort from nearest to farthest:

RankStudentStudy, sleepResultDistance
1G4, 4Fail1.00
2D6, 5Pass1.41
3C2, 3Fail3.16
4E7, 7Pass3.61
5F8, 6Pass3.61
6B2, 1Fail4.24
7A1, 2Fail4.47

Now watch how the answer depends on k. Move the slider and count the votes yourself:

kNeighbours askedVotesPrediction
1G1 FailFail
3G, D, C2 Fail, 1 PassFail
5G, D, C, E, F2 Fail, 3 PassPass
7Everyone4 Fail, 3 PassFail

How to choose k

kWhat happensProblem
Too small (k = 1)One odd example decides everything. Training accuracy is 100%, because each point is its own nearest neighbour.Overfitting
Too largeFar-away examples get a vote, and local patterns blur away.Underfitting
In betweenEnough voices to ignore noise, close enough to stay relevant.Find it by testing
  • Use cross-validation: try several values of k and keep the one with the best average score. Code is below.
  • With two classes, use an odd k so votes can't tie.
  • The popular shortcut k ≈ √n (n = number of training examples) is only a starting point, not an answer.

Distance metrics

“Nearest” needs a definition. Here are the common ones, measured between the new student (5, 4) and student F (8, 6):

MetricFormulaHereThink of it as
Euclidean√Σ(xᵢ − yᵢ)²√(9 + 4) = 3.61A straight line, as a bird flies
ManhattanΣ|xᵢ − yᵢ|3 + 2 = 5Walking along city blocks
Minkowski(Σ|xᵢ − yᵢ|ᵖ)^(1/p)p = 1 and p = 2 give the two aboveThe general formula
HammingCount of positions that differFor text and categoriesSpot the difference

Why scaling is not optional

This is the mistake beginners make most often with KNN. Compare a 26-year-old earning ₹53,000 with two people: one is 25 and earns ₹50,000, the other is 60 and earns ₹52,000.

why_scale.py
import math

new_person = (26, 53000)
young = (25, 50000)
old = (60, 52000)

print("Without scaling")
print("  to the 25-year-old:", round(math.dist(new_person, young), 1))
print("  to the 60-year-old:", round(math.dist(new_person, old), 1))

# Put both features on a similar scale: age / 50, income / 20000
def scale(person):
    return (person[0] / 50, person[1] / 20000)

print("With scaling")
print("  to the 25-year-old:", round(math.dist(scale(new_person), scale(young)), 2))
print("  to the 60-year-old:", round(math.dist(scale(new_person), scale(old)), 2))

Output

Without scaling
  to the 25-year-old: 3000.0
  to the 60-year-old: 1000.6
With scaling
  to the 25-year-old: 0.15
  to the 60-year-old: 0.68

Without scaling, the 60-year-old looks “closer”, because income numbers are huge and drown out age. After scaling, the 25-year-old is correctly closer. In real work, use StandardScaler, as the scikit-learn code below does.

KNN from scratch in Python

This runs the worked example above, with no libraries beyond Python's own. Press Run to try it, then change the new student.

knn_scratch.py
import math
from collections import Counter

# (hours studied, hours slept), result
students = [
    ((1, 2), "Fail"), ((2, 1), "Fail"), ((2, 3), "Fail"), ((4, 4), "Fail"),
    ((6, 5), "Pass"), ((7, 7), "Pass"), ((8, 6), "Pass"),
]

def predict(new_student, k):
    # Step 2: measure the distance to everyone
    distances = [(math.dist(new_student, point), result) for point, result in students]
    # Step 3: keep the k closest
    nearest = sorted(distances, key=lambda row: row[0])[:k]
    # Step 4: majority vote
    votes = Counter(result for _, result in nearest)
    return votes.most_common(1)[0][0]

for k in [1, 3, 5, 7]:
    print("k =", k, "->", predict((5, 4), k))

Output

k = 1 -> Fail
k = 3 -> Fail
k = 5 -> Pass
k = 7 -> Fail

KNN with scikit-learn, the way it's done at work

Here is a real dataset: 150 iris flowers, 4 measurements each, 3 species. A pipeline scales the data, cross-validation picks k, and the held-back test set gives an honest final score.

knn_iris.py
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split, cross_val_score
from sklearn.neighbors import KNeighborsClassifier
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler

X, y = load_iris(return_X_y=True)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.25, random_state=42, stratify=y)

# Try each k using only the training data
best_k, best_score = None, 0
for k in [1, 3, 5, 7, 9, 11, 15, 21]:
    model = make_pipeline(StandardScaler(), KNeighborsClassifier(n_neighbors=k))
    score = cross_val_score(model, X_train, y_train, cv=5).mean()
    print(f"k={k:2}  cross-validation accuracy {score:.3f}")
    if score > best_score:
        best_k, best_score = k, score

# Only now touch the test set, once
final = make_pipeline(StandardScaler(), KNeighborsClassifier(n_neighbors=best_k))
final.fit(X_train, y_train)
print("Best k:", best_k)
print(f"Test accuracy: {final.score(X_test, y_test):.3f}")

Output

k= 1  cross-validation accuracy 0.946
k= 3  cross-validation accuracy 0.946
k= 5  cross-validation accuracy 0.946
k= 7  cross-validation accuracy 0.955
k= 9  cross-validation accuracy 0.955
k=11  cross-validation accuracy 0.955
k=15  cross-validation accuracy 0.946
k=21  cross-validation accuracy 0.902
Best k: 7
Test accuracy: 0.947

Predicting a number: KNN regression

Swap the vote for an average, and KNN predicts numbers.

knn_regression.py
from sklearn.neighbors import KNeighborsRegressor

# House size in square feet, and price in lakh rupees
sizes = [[500], [700], [900], [1100], [1300], [1500]]
prices = [20, 28, 35, 44, 52, 60]

model = KNeighborsRegressor(n_neighbors=2).fit(sizes, prices)
print("Predicted price for 1000 sq ft:", model.predict([[1000]])[0], "lakh")

Output

Predicted price for 1000 sq ft: 39.5 lakh

Speed and the curse of dimensionality

StageWork doneWhy
TrainingAlmost noneIt only stores the data
One prediction, brute forceO(n × d)Distance to all n examples, across d features
One prediction, KD-tree or ball treeOften much faster at low dIt skips regions that can't be nearest. The benefit fades as d grows.

With many features, every point ends up about equally far from every other, so “nearest” stops meaning much. This is the curse of dimensionality. Remove useless features or use PCA before KNN.

Strengths, weaknesses and where it's used

StrengthsWeaknesses
Simple to understand and explainSlow predictions on large data
No training time, and new data is easy to addMust keep all the data in memory
Handles curved boundaries and many classesSensitive to scale, noise and irrelevant features
  • Recommendations: “people similar to you also liked this”.
  • Filling in missing values from similar rows (KNNImputer).
  • Searching for similar images or documents using embeddings.
  • A quick baseline: a simple score that a fancier model must beat.

Common mistakes

  • Forgetting to scale features.
  • Fitting the scaler on all the data before splitting, which leaks test information. A pipeline prevents this.
  • Choosing k using the test set. Use cross-validation on the training data.
  • Trusting accuracy when one class is rare. A large k may always predict the common class.

Interview questions

Why is KNN called a lazy learner?

It builds no model during training. It stores the data and does the work at prediction time.

Is KNN parametric?

No. It has no fixed set of learned parameters, and what it keeps grows with the data. k is a hyperparameter that you choose.

What is training accuracy at k = 1?

100%, unless identical points have different labels, because each point is its own nearest neighbour. It tells you nothing about new data.

How would you speed up KNN on millions of rows?

Reduce dimensions, use KD-trees or ball trees at low dimensions, or use approximate nearest-neighbour search such as FAISS or HNSW, trading a little accuracy for speed.

KNN vs K-means?

KNN is supervised and predicts from labelled neighbours. K-means is unsupervised and forms groups with no labels. In one, k means neighbours; in the other, k means clusters.

Practice

  1. By hand: using the table above, what does k = 3 predict for a student at (7, 5)? Check with the scratch code.
  2. Change the scratch code to use Manhattan distance. Does any prediction change?
  3. In the iris code, remove StandardScaler. Does the score change? Why might iris be less affected than the income example?

Read it. Now try it yourself.

Play with KNN: the interactive lesson

Finished reading?

Sources: scikit-learn: Nearest Neighbors · Cover & Hart (1967), Nearest neighbor pattern classification