🌙 30-Day AI Ramadan Series  ·  Day 6 of 30
Deep Dive · Feb 24, 2026 · 15 min read · ML Models

K-Nearest Neighbors:
Predictions by Association

KNN stores nothing, learns nothing, and yet achieves remarkable results. Understanding it unlocks the intuition behind vector databases, semantic search, and modern RAG systems.

← Back to Blog ☰ Categories

You move to a new neighborhood. You want to know: what kind of area is this?
You don't read the city report. You don't analyze census data.
You look at the five houses nearest to you.
Three are families with children. One is a young couple. One is a retired person.
You conclude: this is a family neighborhood. Probably safe. Probably quiet.

You just ran K-Nearest Neighbors with K = 5.

01 — The Laziest Model in Machine Learning

KNN has a radical design philosophy: do absolutely nothing during training. No weights to learn, no equations to fit, no optimization loop. Just take the entire training dataset and store it in memory. Every other algorithm we've seen — linear regression, logistic regression — spends training time compressing the data into a small set of learned parameters. KNN refuses to compress anything. It keeps everything.

All the real work happens at prediction time. When a new data point arrives asking for a prediction, KNN searches through every stored training point, finds the K closest ones, and asks them to vote:

1
Receive a new point you want to classify or predict a value for. This point has features (e.g. age, income, height) but no label yet.
2
Measure the distance from the new point to every single point in the training dataset. This produces N distance values — one per training example.
3
Select the K smallest distances — the K nearest neighbors. These are the training points most similar to the new one.
4
Make the prediction:
— Classification: the K neighbors vote. The most common class among them wins.
— Regression: take the average of the K neighbors' target values.

This is why KNN is called a lazy learner or instance-based learner — it defers all computation to prediction time, and the training data itself is the model. There are no parameters. There is no "learned function." Just data, and the assumption that similar inputs produce similar outputs.

🩺 Analogy

A doctor with 30 years of experience sees a new patient. Rather than applying a rigid rulebook, she mentally searches her memory: "I've seen patients with these exact symptoms before. Most of them had condition X." Her memory of past cases is her model. Their similarity to the current patient guides her prediction. KNN reasons the same way — by analogy, not by rules.

02 — Distance: The Heart of KNN

Everything in KNN depends on one question: how do you define "closeness" between two data points? The answer is a distance metric — a mathematical formula that takes two points and returns a number. Smaller number = more similar.

Let's make this concrete. Suppose you have two people described by two features: Age and Income (in thousands).

Person A: (30, 50)  ·  Person B: (34, 53)

Euclidean distance — straight-line
Formula: d = √( (x₂−x₁)² + (y₂−y₁)² )

Example: √( (34−30)² + (53−50)² ) = √( 16 + 9 ) = √25 = 5.0

Think of it as the length of the straight line connecting two dots on graph paper. Best for continuous, geometrically meaningful features where the shortest path between two points is a straight line.
Manhattan distance — city block
Formula: d = |x₂−x₁| + |y₂−y₁|

Example: |34−30| + |53−50| = 4 + 3 = 7.0

Think of it as navigating a city grid — you can only travel along streets, not diagonally through buildings. Better when features are independent and diagonal movement isn't meaningful (e.g. pixel coordinates, checklist features).
Cosine similarity — angle between vectors
Formula: similarity = (A·B) / (|A| × |B|)

Measures the angle between two vectors, ignoring their length. Score of 1 = same direction (identical). Score of 0 = perpendicular (unrelated). Score of −1 = opposite directions.

When to use it: text and embedding spaces, where magnitude carries no useful information. A short tweet and a long article about the same topic should be "similar" regardless of word count. Euclidean distance would call them far apart. Cosine similarity correctly calls them close.
Minkowski distance — the generalizer
Formula: d = ( Σ|xᵢ−yᵢ|ᵖ )^(1/p)

A single formula with a tunable exponent p. When p=1 it becomes Manhattan. When p=2 it becomes Euclidean. Larger p gives more weight to the biggest difference between any two dimensions. Useful when you want to tune how much outlier dimensions are penalized.
⚠️ Critical rule: always scale your features first

KNN computes distances across all features simultaneously. If one feature has a large numerical range and another has a small one, the large-range feature will completely dominate every distance calculation — the other feature might as well not exist.

Concrete example: predicting house prices with two features — number of rooms (range: 1–10) and square footage (range: 500–5000). Without scaling:

House A: (3 rooms, 800 m²)  ·  House B: (3 rooms, 810 m²)  ·  House C: (8 rooms, 801 m²)

Euclidean distance A→B: √(0² + 10²) = 10.0
Euclidean distance A→C: √(5² + 1²) = 5.1

KNN thinks C is closer to A than B is — because the 5-room difference (5 units) looks smaller than the 10 m² difference (10 units). This is backwards. Apply StandardScaler first and the problem disappears: every feature gets mean=0 and std=1, so all dimensions contribute equally to distance.

03 — Choosing K: The Bias-Variance Dial

K is the only hyperparameter KNN has, and it controls the fundamental tradeoff between overfitting and underfitting. Think of K as a dial that adjusts how much you trust individual neighbors vs the crowd.

K = 1  →  Maximum overfitting
The prediction is based on a single neighbor — the closest one. The model perfectly memorizes all training data: 0% training error. But it creates jagged, noisy decision boundaries. A single outlier in the training data carves out its own little prediction island. Any noise in the training set is directly inherited by the model.

Imagine asking for a restaurant recommendation from one random passerby — you might get great advice, or you might get their single worst experience.
K = N (all neighbors)  →  Maximum underfitting
Every training point gets a vote. The prediction is always the majority class in the entire dataset — regardless of where the new point is. The model completely ignores the input. It will always say "spam" if spam is 60% of training data, no matter what the email looks like.

Imagine asking every person in a stadium for a restaurant recommendation — the result is always "McDonald's" because it's the most popular, no matter what cuisine you want.
K = 5 to 15  →  Usually the sweet spot
A small local crowd votes. The boundary is smoother than K=1 but still responsive to local patterns. Use cross-validation to find the exact best K for your dataset — don't just guess. Rule of thumb: try all odd values from 1 to 20 and pick the K with the best validation score. Always use an odd K for binary classification to avoid tie votes.

04 — From Scratch: KNN in Pure Python

KNN is one of the easiest algorithms to implement from scratch — no calculus, no matrices, just loops and distance formulas. Reading this makes the sklearn version completely transparent.

import numpy as np
from collections import Counter

def euclidean(a, b):
    """Straight-line distance between two points."""
    return np.sqrt(np.sum((a - b) ** 2))

def knn_predict(X_train, y_train, x_new, k=3):
    """Classify x_new using the k nearest neighbors in X_train."""
    # Step 1: compute distance from x_new to every training point
    distances = [euclidean(x_new, x_train) for x_train in X_train]

    # Step 2: get the indices of the k smallest distances
    k_indices = np.argsort(distances)[:k]

    # Step 3: look up the labels of those k neighbors
    k_labels = [y_train[i] for i in k_indices]

    # Step 4: majority vote — return the most common label
    most_common = Counter(k_labels).most_common(1)
    return most_common[0][0]

# Simple 2D dataset: two clusters
#   Class 0 = bottom-left region
#   Class 1 = top-right region
X_train = np.array([[1,2],[2,3],[3,1],[6,5],[7,8],[8,6]])
y_train = np.array([0, 0, 0, 1, 1, 1])

x_new = np.array([5, 4])   # unknown point — which cluster?
prediction = knn_predict(X_train, y_train, x_new, k=3)
print(f"Nearest 3 neighbors vote: class {prediction}")

# Show the distances explicitly
distances = [(i, euclidean(x_new, X_train[i]), y_train[i])
             for i in range(len(X_train))]
distances.sort(key=lambda d: d[1])
print("\nAll training points sorted by distance from (5,4):")
for idx, dist, label in distances:
    print(f"  Point {X_train[idx]} | dist={dist:.2f} | class={label}")
→ output
Nearest 3 neighbors vote: class 1 All training points sorted by distance from (5,4): Point [6 5] | dist=1.41 | class=1 Point [3 1] | dist=3.61 | class=0 Point [7 8] | dist=4.47 | class=1 Point [2 3] | dist=3.16 | class=0 Point [8 6] | dist=3.61 | class=1 Point [1 2] | dist=5.00 | class=0
💡 Reading the output

The new point (5,4) is closest to [6,5] (class 1), then [2,3] (class 0), then [7,8] (class 1). With K=3, the vote is 2 class-1 vs 1 class-0 → prediction: class 1. You can see exactly how every distance was computed. There's no mystery here — just arithmetic.

05 — KNN in sklearn (Real Dataset)

The sklearn version handles scaling, cross-validation, and K selection cleanly. This is the production pattern.

from sklearn.neighbors import KNeighborsClassifier
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split, cross_val_score
from sklearn.preprocessing import StandardScaler
import numpy as np

# Iris dataset: 150 flowers, 4 features, 3 species
X, y = load_iris(return_X_y=True)
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.2, random_state=42)

# ── Scale first — mandatory for KNN ─────────────────────────────────
# fit_transform on train: learn mean/std from training data only
# transform on test: apply the SAME scaling (never refit on test!)
scaler  = StandardScaler()
X_train = scaler.fit_transform(X_train)
X_test  = scaler.transform(X_test)

# ── Find best K via 5-fold cross-validation ──────────────────────────
cv_scores = [cross_val_score(KNeighborsClassifier(k), X_train, y_train, cv=5).mean()
             for k in range(1, 21)]
best_k = np.argmax(cv_scores) + 1
print(f"Best K = {best_k}")
print(f"CV accuracy at best K = {cv_scores[best_k-1]:.2%}")

# ── Final evaluation on held-out test set ───────────────────────────
model = KNeighborsClassifier(best_k).fit(X_train, y_train)
print(f"Test accuracy = {model.score(X_test, y_test):.2%}")
→ output
Best K = 7 CV accuracy at best K = 96.67% Test accuracy = 96.67%
📌 Why fit_transform on train but only transform on test?

When you call fit_transform, the scaler learns the mean and standard deviation from that data, then applies the scaling. If you were to refit on the test set, the test data would be scaled using its own statistics — which is a form of data leakage. The test set must be scaled with the exact same mean and std learned from the training set. This simulates what happens in production: the scaler never sees future data.

06 — The Curse of Dimensionality

KNN's single biggest weakness is high-dimensional data. As the number of features grows, something counterintuitive and devastating happens to the concept of distance.

🧊 Intuition: the expanding empty space

In 1 dimension: imagine a line of length 1. Place two random points on it. On average they're about 0.33 apart — relatively close.

In 2 dimensions: a unit square. Two random points average ~0.52 apart. A bit farther.

In 100 dimensions: a unit hypercube. Two random points are now about 4.1 apart — and crucially, almost all pairs of random points are approximately the same distance from each other.

When every point is roughly equidistant from every other point, the concept of "nearest neighbor" becomes meaningless. Your closest neighbor is almost as far away as your farthest one. The algorithm can no longer discriminate.

Practically: KNN works well with 2–50 features. Above 100 features, performance degrades noticeably. Above 500, it often becomes useless without intervention.

✓
Apply PCA or UMAP first. Dimensionality reduction compresses high-dimensional data into a lower-dimensional space while preserving as much structure as possible. Run PCA to reduce to 20–50 components, then apply KNN to the result.
PCA is linear — it finds the directions of maximum variance. UMAP is non-linear and often preserves local neighborhood structure better, making it a natural fit as a preprocessing step for KNN.
✓
Remove irrelevant features. Every useless feature adds noise to the distance calculation. Feature selection (keeping only the most informative features) can dramatically improve KNN in high dimensions.
A useful heuristic: if you have N training examples, keep at most √N features for KNN to work reliably. With 1000 training examples, that's roughly 30 features max.
✓
Switch to a model that handles high dimensions better. Random forests, gradient boosting, and neural networks are far less sensitive to dimensionality. If you have 500+ features, KNN is rarely the right tool.
🔗 Connection to Modern AI — Vector Databases & RAG

FAISS, Pinecone, Chroma, and Weaviate — the vector databases powering production RAG systems — are all implementations of approximate K-Nearest Neighbor search in high-dimensional embedding space.

Here's what that means concretely: when a language model reads a document, it converts it into a list of 768 or 1536 numbers — a vector embedding that encodes the document's meaning as a point in high-dimensional space. Documents about similar topics land near each other in that space. When you ask a RAG system a question, it converts your question into an embedding vector, then finds the K training documents whose embeddings are nearest — the K most semantically similar chunks.

That retrieval step is KNN. The vector database is a highly optimized, approximate KNN index. KNN isn't old technology — it's the retrieval backbone of every production LLM-powered application.

07 — When KNN Wins (and When It Loses)

✓ Use KNN when…
Your dataset is small to medium (<50,000 examples)
You have few, well-scaled features (2–50)
The decision boundary is complex and non-linear
You need a quick, interpretable baseline with no training time
The data has clear local structure (clusters)
✗ Avoid KNN when…
Your dataset is large (>100,000 examples) — prediction is O(N) per query
You have many features (100+) — curse of dimensionality
Features are on very different scales and you can't preprocess
You need fast real-time predictions — KNN must scan the entire dataset
You have many irrelevant features — they poison the distance calculation

08 — A Thought to Sit With

💬 For You to Consider

KNN assumes that similar inputs produce similar outputs. But "similar" depends entirely on the space you measure distance in. Two photos of the same person in different lighting conditions are very "distant" in raw pixel space — millions of pixels differ. But in a learned embedding space (like a face recognition model produces), they are "close" — they represent the same identity.

What does this tell you about where the real intelligence lives — in the algorithm, or in the representation it operates on? And what does that imply about what "learning" really means?

09 — Coming Up Next

Tomorrow — Day 7: Naive Bayes. A probabilistic model that classifies spam before you even open it. It makes an absurd mathematical assumption — that all features are completely independent of each other — and somehow works brilliantly anyway. Understanding why it works despite being "wrong" teaches something deep about the relationship between model assumptions and real-world performance.

10 — Go Deeper

▶ StatQuest: K-Nearest Neighbors, Clearly Explained
YouTube · 9 min · The clearest short introduction to KNN — great for cementing the intuition after reading this post.

▶ Kaggle: Intro to Machine Learning
Free interactive course · Apply KNN and other classifiers to real datasets in your browser. The fastest way to build hands-on intuition.

▶ Coursera: Deep Learning Specialization — Andrew Ng
5-course specialization · Covers embeddings and vector representations that make KNN powerful at scale — the bridge from this post to modern AI retrieval systems.

🧠 Check Your Understanding
1. KNN is called a "lazy learner." What does that mean, and what is the practical consequence at prediction time?
Lazy learner means KNN does no work during training — it simply stores the entire dataset in memory. No parameters are learned, no optimization happens. The practical consequence is that all the computation is deferred to prediction time: every time you want a prediction, KNN must measure the distance from the new point to every single training point. Training is O(1) (instant), but prediction is O(N·D) — it scales linearly with the number of training examples N and dimensions D. For large datasets, this makes prediction very slow.
2. You have two features: height in cm (range 150–200) and weight in kg (range 50–100). You apply KNN without scaling. What goes wrong?
Both features have similar ranges here, so this specific example would be okay. But consider if weight were in grams (50,000–100,000) instead — then the distance calculation would be almost entirely determined by weight. Height would contribute almost nothing. More generally: whenever one feature spans a much larger numerical range than another, the larger-range feature dominates the Euclidean distance and the smaller-range feature is effectively ignored. StandardScaler corrects this by making each feature have mean=0 and std=1, so all features contribute equally. This is not optional for KNN — it is required.
3. What happens when K=1 versus K=N? Explain both in terms of overfitting and underfitting.
K=1: the prediction is based on a single nearest neighbor. This perfectly memorizes all training data — training error is 0% — but the model is extremely sensitive to noise. A single mislabeled or unusual training point creates its own prediction region, causing the model to misclassify new nearby points. This is maximum overfitting. K=N (all training examples): every training point votes for every prediction. The result is always the majority class in the training set, completely ignoring where the new point actually is in feature space. Training accuracy is at best equal to the class frequency of the dominant class. This is maximum underfitting. The right K sits between these extremes, and cross-validation finds it.
4. Why would you use cosine similarity instead of Euclidean distance for text classification?
In text data represented as word counts or TF-IDF vectors, two documents about the same topic but of very different lengths will have very different magnitudes — a long article has much larger word counts than a short tweet on the same topic. Euclidean distance would call them far apart because of this magnitude difference, even though they discuss the same content. Cosine similarity measures the angle between the vectors, ignoring magnitude entirely. The long article and the short tweet point in the same direction in word-space (same topic distribution), so their cosine similarity is high. Whenever magnitude is noise and direction is signal — text, embeddings, recommendation systems — use cosine similarity.
5. What is the curse of dimensionality, and what are two ways to address it before applying KNN?
In high-dimensional space, all data points become approximately equidistant from each other. The ratio of the furthest neighbor's distance to the nearest neighbor's distance approaches 1 as dimensions increase — meaning "nearest" becomes meaningless. KNN fails because it can no longer discriminate between close and far points. Two solutions: (1) Apply dimensionality reduction first — PCA compresses data linearly by finding directions of maximum variance; UMAP does so non-linearly and preserves local structure especially well. Reduce to 20–50 dimensions before running KNN. (2) Feature selection — remove irrelevant features that add noise to the distance calculation. Only features that are genuinely predictive should remain.
⚡ Quick Recap — K-Nearest Neighbors
  1. Lazy learner — No training. Stores all data. All computation happens at prediction time.
  2. Prediction — Find the K nearest training points. Vote (classification) or average (regression).
  3. Distance metrics — Euclidean (straight line), Manhattan (city block), Cosine (angle, ignores magnitude), Minkowski (generalizes both).
  4. Feature scaling is mandatory — Large-range features dominate distance. Apply StandardScaler before KNN, always.
  5. Choosing K — K=1 overfits. K=N underfits. Use cross-validation on odd values 1–20 to find the best K.
  6. Complexity — Training: O(1). Prediction: O(N·D) per query — slow at scale.
  7. Curse of dimensionality — Above ~100 features, all points become equidistant. Apply PCA/UMAP or feature selection first.
  8. Cosine similarity — Use when magnitude is noise and direction is signal: text, embeddings, recommendation systems.
  9. In modern AI — Vector databases (FAISS, Pinecone, Chroma) are optimized approximate KNN indices. RAG retrieval is KNN on embeddings.
  10. Best for — Small datasets, low–medium dimensions, complex non-linear boundaries, quick interpretable baselines.
🎯 Interview Questions
  1. Why must you scale features before using KNN?
    KNN relies purely on distance. Features with larger numerical ranges dominate the calculation — the model effectively ignores smaller-range features. StandardScaler normalizes every feature to mean=0 and std=1, ensuring equal contribution to every distance.
  2. What is the curse of dimensionality and how does it affect KNN?
    In high dimensions, all data points become approximately equidistant. The ratio of nearest-to-farthest neighbor distance approaches 1, making "nearest neighbor" meaningless. KNN degrades sharply above ~100 features. Solutions: PCA/UMAP dimensionality reduction or feature selection before applying KNN.
  3. What is the effect of K=1 vs K=N?
    K=1: perfect memorization, 0% training error, maximum overfitting — every outlier creates its own prediction region. K=N: always predicts the majority class, completely ignores input — maximum underfitting. The right K balances these extremes; find it with cross-validation.
  4. When would you choose cosine similarity over Euclidean distance?
    When vector magnitude carries no useful information but direction does — text, embeddings, recommendation systems. Two documents about the same topic but different lengths have similar direction (topic distribution) but very different magnitudes (word counts). Cosine similarity correctly identifies them as close; Euclidean distance would not.
  5. What are the time complexity tradeoffs of KNN?
    Training: O(1) — just store data. Prediction: O(N·D) per query where N is training size and D is dimensions — must scan all training examples. This makes KNN fast to train but slow at prediction scale. Approximate methods (FAISS, Annoy, HNSW) reduce prediction cost to O(log N) at the cost of some accuracy.
📎 Attachments & Further Reading

No attachments for this topic yet.

#KNN#KNearestNeighbors#DistanceMetrics#VectorSearch#MLModels#30DaysOfAI#Ramadan2026#sklearn
← Day 5 Logistic Regression