Live Coding Interview Prep

Course Content

Live Coding Interview Prep

7 sections · 50 lessons

Write cosine similarity, dot product, and Euclidean distance functions manually.


What you need to know

measureformularangemeaning
dot productsum of a[i] × b[i]any numberalignment and length
cosine similaritydot / (|a| × |b|)-1 to 1alignment only
Euclidean distancesqrt(sum of (a[i] − b[i])²)0 to ∞straight-line gap; smaller is closer
  • Dot product grows with vector length, so on raw vectors it favours long vectors. It is only a similarity measure when vectors are normalised.
  • Cosine divides out the lengths, leaving only the angle.
  • Euclidean is a distance: 0 means identical, and you sort ascending.

For unit vectors (length 1) they are tied together:

Text
|a − b|² = |a|² + |b|² − 2(a · b) = 1 + 1 − 2 cos = 2 − 2 cos

So ranking by highest cosine and by lowest Euclidean distance gives the same order. That is why vector databases normalise once and then use the cheapest one, the dot product.

Python
import mathfrom collections.abc import Sequencedef _check(a: Sequence[float], b: Sequence[float]) -> None:    if len(a) != len(b):        raise ValueError(f"length mismatch: {len(a)} vs {len(b)}")def dot(a: Sequence[float], b: Sequence[float]) -> float:    """Sum of element-wise products."""    _check(a, b)    return sum(x * y for x, y in zip(a, b))def magnitude(a: Sequence[float]) -> float:    """L2 length. math.hypot scales internally, so huge values do not overflow."""    return math.hypot(*a)def cosine_similarity(a: Sequence[float], b: Sequence[float]) -> float:    """Cosine of the angle between a and b; 0.0 if either is a zero vector."""    _check(a, b)    ma, mb = magnitude(a), magnitude(b)    if ma == 0 or mb == 0:        return 0.0    c = sum((x / ma) * (y / mb) for x, y in zip(a, b))   # normalise first, then dot    return max(-1.0, min(1.0, c))                          # clip rounding errordef euclidean_distance(a: Sequence[float], b: Sequence[float]) -> float:    _check(a, b)    return math.hypot(*(x - y for x, y in zip(a, b)))     # sqrt of summed squares

The tricky parts:

  • Check lengths first, in every function. zip([1, 2, 3], [1, 2]) quietly stops after two pairs, so without the check you get a wrong answer and no error. The check also has to come before the zero-vector shortcut in cosine_similarity, or a mismatched pair with a zero vector returns 0.0 silently.
  • Zero vector. Cosine is undefined for a zero vector. Returning 0.0 ("no similarity") is the common convention; raising is also defensible — say which you chose.
  • Large values. The textbook dot(a, b) / (|a| × |b|) overflows: for a = [1e200, 1e200] both the dot product and the product of lengths become inf, and inf / inf is nan. Dividing each element by its vector's length before multiplying keeps every number between -1 and 1, and math.hypot computes the length without squaring huge numbers directly.
  • Floating point. Rounding can push a cosine to 1.0000000000000002, so the result is clipped to [-1, 1]. In tests, compare with math.isclose, never ==.

Complexity: each function makes one or two passes over d elements, so O(d) time. math.hypot(*...) unpacks the values into arguments, so it uses O(d) temporary space; a plain sum of squares would be O(1) extra space but can overflow — (1e200) ** 2 raises OverflowError. The NumPy versions are a @ b, a @ b / (np.linalg.norm(a) * np.linalg.norm(b)) and np.linalg.norm(a - b).

A real-life example

Take a = [1, 2, 2] and b = [2, 0, 1]:

stepcomputationvalue
dot1×2 + 2×0 + 2×14
|a|sqrt(1 + 4 + 4)3.0
|b|sqrt(4 + 0 + 1)2.236
cosine4 / (3 × 2.236)0.5963
Euclideansqrt(1 + 4 + 1)2.4495
Python
a, b = [1, 2, 2], [2, 0, 1]print(dot(a, b), round(cosine_similarity(a, b), 4), round(euclidean_distance(a, b), 4))# 4 0.5963 2.4495ua = [x / magnitude(a) for x in a]ub = [x / magnitude(b) for x in b]print(round(euclidean_distance(ua, ub) ** 2, 4), round(2 - 2 * cosine_similarity(a, b), 4))# 0.8074 0.8074print(cosine_similarity([0, 0, 0], a))                  # 0.0print(cosine_similarity([1e200, 1e200], [1e200, 1e200]))  # 0.9999999999999998, not nan

The last two lines confirm the unit-vector identity on real numbers, which is a nice thing to show the interviewer after writing the code.

Deduplicating near-identical product descriptions or support tickets is often just "cosine above 0.95 means duplicate", computed with exactly these functions.

Follow-up questions to expect

  • "Which should a vector database use?" — Normalise at write time and use dot product: it gives the cosine ranking with the fewest operations.
  • "Is cosine distance a true metric?" — 1 − cosine does not satisfy the triangle inequality in general; angular distance (arccos(cosine) / π) does. It matters for some tree-based indexes, rarely in practice.
  • "How would you test these?" — Known pairs: [1, 0] and [0, 1] give cosine 0 and distance √2; identical vectors give cosine 1 and distance 0; and a property test that the unit-vector identity holds on random vectors.