Skip to the solution
MediumSystem Design16 min read

Embeddings in 2025-26: how do you choose model, dimensions, and distance?

972 views

The problem

Question presented to candidate: "We are building retrieval over about ten million document chunks. Walk me through how you would pick the embedding model, how many dimensions to store, and which distance metric to use, and what would make you change those choices later."

What a strong answer should cover:

  • Model choice is an evaluation, not a leaderboard pick: shortlist from MTEB and model cards on hard constraints (languages, max input length, hosting and data rules, price), then measure recall@k or nDCG on your own labelled queries
  • The details that silently cost quality: query and document prefixes or task types, the model input limit versus chunk size, multilingual and domain fit
  • Dimensions are a storage, memory and latency budget: raw float32 is dimensions x 4 bytes per vector; Matryoshka-trained models (OpenAI dimensions parameter, other MRL models) can be truncated and renormalised with small loss, models without that training cannot
  • Precision is a second lever: int8 scalar quantization (4x smaller) loses little; binary (32x) needs rescoring with the full vectors and works best with many dimensions
  • Distance is whatever the model was trained for, normally cosine; normalise once and use dot product, and keep the same metric at index and query time
  • Changing model means re-embedding everything: version the index, dual-write or rebuild alongside, evaluate, then switch

Clarifying questions expected:

  • Which languages and content types (prose, code, tables) must it handle, and how long are the chunks?
  • Can the data be sent to a third-party API, or must the model run in our own environment?
  • What is the latency and memory budget for the index, and how fast does the corpus grow?

Code / implementation expected: Optional โ€” the storage arithmetic for dimensions and precision, or a small recall@k comparison, is a strong signal.

  • embeddings
  • matryoshka

Try it yourself

Sketch your approach before you read the solution. That is what the interview tests.

Stuck? Get a hint.

A short nudge toward the approach. It will not give the answer away.

Solution

The solution is hidden so you can try first

16 min read ยท 11 sections

Read the code

Truncating dimensions and quantizing vectors: recall@10 and storage on synthetic data
// Dimensions and precision: what you lose when you store fewer or smaller numbers.
// Synthetic vectors (seeded). "mrl" puts most signal in the first dimensions, the
// way Matryoshka-trained models are built to; "flat" spreads it evenly.
let seed = 7;
const rand = () => { seed = (seed * 1664525 + 1013904223) % 4294967296; return seed / 4294967296; };
const gauss = () => { let s = 0; for (let i = 0; i < 12; i++) s += rand(); return s - 6; };
const D = 32, N = 1000, Q = 20, K = 10;
const DECAY = [1]; for (let d = 1; d < D; d++) DECAY.push(DECAY[d - 1] * 0.85); // weight of dimension d in "mrl"

function corpus(weight) {
  const docs = [], qs = [];
  for (let i = 0; i < N; i++) { const v = []; for (let d = 0; d < D; d++) v.push(gauss() * weight(d)); docs.push(v); }
  for (let j = 0; j < Q; j++) { const base = docs[j * 37]; qs.push(base.map((x, d) => x + gauss() * 0.6 * weight(d))); }
  return { docs, qs };
}
const dotT = (a, b, t) => { let s = 0; for (let d = 0; d < t; d++) s += a[d] * b[d]; return s; };
const cosT = (a, b, t) => dotT(a, b, t) / Math.sqrt(dotT(a, a, t) * dotT(b, b, t)); // truncate then renormalise
const topK = (scores) => scores.map((s, i) => [s, i]).sort((x, y) => y[0] - x[0] || x[1] - y[1]).slice(0, K).map((x) => x[1]);
function recall(qs, score, truth) {
  let hit = 0;
  qs.forEach((q, j) => { const t = new Set(truth[j]); hit += topK(score(q)).filter((i) => t.has(i)).length; });
  return ((100 * hit) / (Q * K)).toFixed(1) + "%";
}

for (const [name, weight] of [["mrl", (d) => DECAY[d]], ["flat", () => 1]]) {
  const { docs, qs } = corpus(weight);
  const truth = qs.map((q) => topK(docs.map((x) => cosT(q, x, D))));
  const row = [32, 16, 8, 4].map((t) => `${t}d ${recall(qs, (q) => docs.map((x) => cosT(q, x, t)), truth)}`);
  console.log(`${name.padEnd(5)} truncation recall@10: ${row.join("  ")}`);
}

// precision on the flat corpus at full 32 dims
const { docs, qs } = corpus(() => 1);
const truth = qs.map((q) => topK(docs.map((x) => cosT(q, x, D))));
const unit = (v) => { const n = Math.sqrt(dotT(v, v, D)); return v.map((x) => x / n); };
const U = docs.map(unit);
const maxAbs = Math.max(...U.flat().map(Math.abs));
const i8 = U.map((v) => v.map((x) => Math.floor((x / maxAbs) * 127 + 0.5))); // 1 byte per dimension
const bits = U.map((v) => v.map((x) => (x > 0 ? 1 : 0)));              // 1 bit per dimension
const hamming = (a, b) => { let s = 0; for (let d = 0; d < D; d++) if (a[d] !== b[d]) s++; return s; };
console.log("precision at 32d (flat corpus):");
console.log(`  float32  ${recall(qs, (q) => U.map((x) => dotT(unit(q), x, D)), truth)}`);
console.log(`  int8     ${recall(qs, (q) => { const u = unit(q); return i8.map((x) => dotT(u, x, D)); }, truth)}`);
console.log(`  binary   ${recall(qs, (q) => { const b = unit(q).map((x) => (x > 0 ? 1 : 0)); return bits.map((x) => -hamming(b, x)); }, truth)}`);
const rescore = (q, keep) => {
  const u = unit(q), b = u.map((x) => (x > 0 ? 1 : 0));
  const cand = new Set(bits.map((x, i) => [-hamming(b, x), i]).sort((x, y) => y[0] - x[0] || x[1] - y[1]).slice(0, keep).map((x) => x[1]));
  return U.map((x, i) => (cand.has(i) ? dotT(u, x, D) : -9));
};
console.log(`  binary, rescore top 40 in float   ${recall(qs, (q) => rescore(q, 40), truth)}`);
console.log(`  binary, rescore top 100 in float  ${recall(qs, (q) => rescore(q, 100), truth)}`);

// storage arithmetic at production scale (vectors only, no index overhead)
console.log("storage for 10,000,000 vectors:");
for (const dims of [3072, 1024, 256]) {
  const gb = (bytes) => ((dims * bytes * 1e7) / 1e9).toFixed(1).padStart(6) + " GB";
  console.log(`  ${String(dims).padStart(4)}d  float32 ${gb(4)}  int8 ${gb(1)}  binary ${gb(1 / 8)}`);
}

Discussion

Other approaches, follow-ups and how it went in real interviews.

Sign in to join the discussion.

No comments yet. Share how you would answer it.

Question 22 of 83 in AI engineering

Keep your momentum going.

Mark this question solved to track your progress, then take the next one while it is fresh.

Up nextMedium

When do you use code-based graders versus model-based graders?

Continue