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
- Choose k, the number of neighbours to ask.
- Measure the distance from the new point to every stored example.
- Keep the k closest examples.
- 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:
| Rank | Student | Study, sleep | Result | Distance |
|---|---|---|---|---|
| 1 | G | 4, 4 | Fail | 1.00 |
| 2 | D | 6, 5 | Pass | 1.41 |
| 3 | C | 2, 3 | Fail | 3.16 |
| 4 | E | 7, 7 | Pass | 3.61 |
| 5 | F | 8, 6 | Pass | 3.61 |
| 6 | B | 2, 1 | Fail | 4.24 |
| 7 | A | 1, 2 | Fail | 4.47 |
Now watch how the answer depends on k. Move the slider and count the votes yourself:
| k | Neighbours asked | Votes | Prediction |
|---|---|---|---|
| 1 | G | 1 Fail | Fail |
| 3 | G, D, C | 2 Fail, 1 Pass | Fail |
| 5 | G, D, C, E, F | 2 Fail, 3 Pass | Pass |
| 7 | Everyone | 4 Fail, 3 Pass | Fail |
How to choose k
| k | What happens | Problem |
|---|---|---|
| Too small (k = 1) | One odd example decides everything. Training accuracy is 100%, because each point is its own nearest neighbour. | Overfitting |
| Too large | Far-away examples get a vote, and local patterns blur away. | Underfitting |
| In between | Enough 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):
| Metric | Formula | Here | Think of it as |
|---|---|---|---|
| Euclidean | √Σ(xᵢ − yᵢ)² | √(9 + 4) = 3.61 | A straight line, as a bird flies |
| Manhattan | Σ|xᵢ − yᵢ| | 3 + 2 = 5 | Walking along city blocks |
| Minkowski | (Σ|xᵢ − yᵢ|ᵖ)^(1/p) | p = 1 and p = 2 give the two above | The general formula |
| Hamming | Count of positions that differ | For text and categories | Spot 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.
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.
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.
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.
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
| Stage | Work done | Why |
|---|---|---|
| Training | Almost none | It only stores the data |
| One prediction, brute force | O(n × d) | Distance to all n examples, across d features |
| One prediction, KD-tree or ball tree | Often much faster at low d | It 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
| Strengths | Weaknesses |
|---|---|
| Simple to understand and explain | Slow predictions on large data |
| No training time, and new data is easy to add | Must keep all the data in memory |
| Handles curved boundaries and many classes | Sensitive 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
- By hand: using the table above, what does k = 3 predict for a student at (7, 5)? Check with the scratch code.
- Change the scratch code to use Manhattan distance. Does any prediction change?
- 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 lessonFinished reading?
Sources: scikit-learn: Nearest Neighbors · Cover & Hart (1967), Nearest neighbor pattern classification