Data science / ML / AI interview questions
Questions an interviewer can ask a Data science / ML / AI, grouped by years of experience. Open one to see the strongest answer and why it works.
48 of the 505 questions our bank holds for this job. The rest are in the app.
By subject
- Mathematics
- Statistics
- Python for Data Science
- Machine Learning Fundamentals
- ML Algorithms
- Model Evaluation
- Feature Engineering
- Deep Learning
- NLP
- Computer Vision
- MLOps
- Responsible AI
0–1 years
Why do we split data into train, validation, and test instead of one pile?
The strongest answer: Train fits the model, validation guides choices I still control, test is a last look I do not tune against — two piles collapse those jobs
Why it works: Exactly. Three roles: fit, choose, report. Mixing them is how scores lie to you.
- Train fits; val chooses; test reports.
- Do not tune on test.
- Two piles merge choice and report.
- More data is not a substitute for a held-out look.
What does gradient descent actually do when a model “trains”?
The strongest answer: It nudges the parameters a small step in the direction that lowers the loss, using the gradient as the slope, and repeats until the loss stops improving
Why it works: Exactly — the gradient says which way is uphill, so you step the other way; the learning rate sets how far.
- Step downhill on the loss.
- Gradient: the slope of the loss.
- Move parameters against it, a little.
- Repeat until the loss levels off.
What is a token, and why does an engineer building on an LLM care?
The strongest answer: The unit the model reads and writes — a piece of a word, not a word. Limits, cost and speed are all counted in tokens, and the count varies with language and content
Why it works: Exactly — context limits, billing and latency are all measured in tokens, and code, long numbers or non-English text often take more tokens than the word count suggests.
- Tokens are the model’s unit of text.
- Limits, cost and latency count tokens.
- Words are only a rough estimate.
- Count with the tokenizer when it matters.
def add(item, bag=[]): bag.append(item); return bag — you call add(1) then add(2). What comes back the second time?
The strongest answer: [1, 2] — the default list is created once when the function is defined, so every call without a bag shares it; use bag=None and build a new list inside
Why it works: Exactly. One default object, shared for the life of the function. None plus a fresh list inside is the fix.
- Default arguments are evaluated once, at definition.
- A mutable default is shared by every call.
- Use None and create the value inside the function.
- The bug is silent, not an error.
What is the difference between an INNER JOIN and a LEFT JOIN?
The strongest answer: INNER returns only rows that match on both sides; LEFT returns every row from the left table and fills the right side with NULLs where there is no match — so LEFT is how you keep the customers with no orders
Why it works: Right. The question to ask is: do I want rows with no match to survive?
- INNER: matches on both sides only.
- LEFT: all left rows, NULLs for missing right.
- Use LEFT to keep the ones without a match.
- A WHERE on the right table can silently turn LEFT into INNER.
An interviewer says: “Design a URL shortener.” What do you do first?
The strongest answer: Ask what it must do and at what scale — reads vs writes, expiry, custom aliases, analytics — and state the assumptions you will design against before drawing anything
Why it works: Right. Requirements and numbers first; the boxes follow from them.
- Clarify functional and non-functional requirements first.
- State assumed scale in numbers.
- Only then draw.
- Check assumptions with the interviewer.
A teammate keeps rerunning the test set after each hyperparameter tweak. What do you say?
The strongest answer: Stop — that set is now another validation fold. Freeze a true holdout and only open it when the recipe is locked
Why it works: Exactly. Repeated peeking turns test into val. A holdout you keep opening is not a holdout.
- Repeated test peeks burn the holdout.
- Lock the recipe, then open test once.
- Do not average your peeks.
- Process beats “I will not look.”
What is the difference between supervised and unsupervised learning, and how do you tell which one a problem needs?
The strongest answer: Supervised learns from examples with a known target and predicts it for new rows; unsupervised finds structure — clusters, low dimensions, anomalies — with no target; the question is whether a trustworthy label exists
Why it works: Exactly — the presence of a target you trust is the deciding fact; if you want to predict a thing, you need examples of it.
- Supervised: a known target exists.
- Unsupervised: find structure, no target.
- Ask: do I trust the labels?
- Clusters are not predictions.
When would you choose a tuple over a list?
The strongest answer: When the collection is fixed and its position means something — a coordinate, a record, a dict key — since tuples are immutable and therefore hashable
Why it works: Right. Immutability is the point, and hashability is what it buys you.
- Tuple = fixed, positional, hashable.
- List = a sequence you intend to change.
- Only a tuple can key a dict or join a set.
- Choose on meaning, not on length.
Why does WHERE status != 'closed' not return rows where status is NULL?
The strongest answer: Because NULL is unknown, and any comparison with NULL is neither true nor false — the row is filtered out; use IS NULL or COALESCE to include them deliberately
Why it works: Exactly. Three-valued logic: comparisons with NULL are unknown and WHERE keeps only true.
- NULL means unknown, not empty.
- Comparisons with NULL are unknown, so WHERE drops them.
- IS NULL / IS NOT NULL to test.
- COALESCE to give NULL a stand-in value.
What does a load balancer do, and what does it not do?
The strongest answer: It spreads incoming requests across several identical servers and stops sending to ones that fail health checks; it does not make one slow server faster or fix a database bottleneck behind them
Why it works: Right. Distribution and failover, nothing more.
- Distributes requests across servers.
- Removes unhealthy servers.
- Not a cache, not capacity.
- Bottlenecks behind it remain.
Fraud is 1% of rows. The model is “99% accurate.” What is the problem?
The strongest answer: Accuracy can be a majority-class trophy — I ask what happens to the rare class and I name a metric that matches the decision
Why it works: Exactly. Quote the decision, then the metric. A 99% score on a 1% event can be “always say no.”
- Accuracy ignores class rarity.
- Name FN/FP costs, then the metric.
- A majority classifier can look “accurate.”
- No invented lift percentages.
1–3 years
Users appear across months. How do you validate a churn model?
The strongest answer: Hold out later months and whole users — I do not let last month’s behaviour of the same person sit in train while this month is test
Why it works: Exactly. Time and identity. Both can leak. A later window of new (or held-out) people is the adult cut.
- Hold out later time and users.
- Do not shuffle calendar blocks.
- Do not test the past with the future.
- One month is a snapshot, not a study.
You read a 40 GB log file to count matching lines. What shape does the code take?
The strongest answer: Iterate the file object line by line, or write a generator that yields matches — one line is in memory at a time, so the file size stops mattering
Why it works: Exactly. Streaming turns memory cost into a constant.
- Iterating a file yields lines lazily.
- Generators keep memory constant.
- readlines and read materialise everything.
- yield lets you compose stages without buffering.
A query is slow. What is the first thing you run, and what are you looking for?
The strongest answer: EXPLAIN (with ANALYZE where safe) to see the plan: a sequential scan on a big table, an estimated row count far from the actual, a nested loop over many rows, or a sort spilling to disk
Why it works: Right. The plan tells you where the time goes before you change anything.
- EXPLAIN first, change second.
- Scans, bad estimates, nested loops, disk sorts.
- ANALYZE shows real times and counts.
- Fix the biggest node.
URL shortener: how do you generate the short code?
The strongest answer: Either hash the long URL and take the first characters, handling collisions by retry, or hand out pre-generated unique codes from a counter or key service; say which, and why — a counter gives guaranteed uniqueness but predictable codes, a hash is stateless but needs collision checks
Why it works: Right. Name the scheme and its trade-off.
- Hash-and-truncate or pre-generated keys.
- Handle collisions explicitly.
- Predictability vs statelessness.
- Length from the target count.
Your assistant confidently describes an old version of a library that had a major release last month. What is going on, and what do you change?
The strongest answer: Its knowledge stops at a training cutoff and it cannot tell what has changed since. I put today’s date and current sources in the context, and have it flag what those sources do not cover
Why it works: Exactly — the fix is in the context: current date, current sources, and permission to say “I cannot confirm that”.
- Knowledge ends at the training cutoff.
- Give it the date and fresh sources.
- Let it say what it cannot confirm.
- A disclaimer is not a fix.
A column is named `will_renew_next_quarter` in a “features” table. Your move?
The strongest answer: Treat it as a label until proven otherwise — check lineage and when it is written — then remove it from features if it is the outcome
Why it works: Exactly. Names lie. Lineage and write-time tell you if it is the target.
- Lineage over column names.
- Outcomes are not features.
- Warehouse ≠ allowed.
- Do not train on the label in costume.
When would you reach for k-nearest neighbours, and what bites people who use it carelessly?
The strongest answer: Small, low-dimensional data where local similarity is meaningful; it has no training cost but every prediction scans the data, it needs scaled features because distance is the model, and a small k is noisy
Why it works: Right — k-NN is a distance argument; scale, dimensionality and k are the whole story, and prediction cost grows with the data.
- k-NN: distance is the model.
- Scale the features.
- Pick k on validation.
- Prediction cost grows with data.
What is a decorator, really?
The strongest answer: A function that takes a function and returns a replacement — @log above def f is just f = log(f), which is why the wrapper should carry functools.wraps
Why it works: Right, and wraps is the part people leave out until a name or docstring goes missing.
- @d over f means f = d(f).
- The decorator runs at definition time.
- functools.wraps keeps __name__ and __doc__.
- A decorator with arguments is one more layer.
An index on (customer_id, created_at) — which queries can use it?
The strongest answer: Ones that filter on customer_id, or on customer_id and created_at; a query filtering only on created_at cannot use it, because the leftmost column comes first in the index order
Why it works: Right. Leftmost prefix — column order is the design decision.
- Composite index serves leftmost prefixes.
- Column order matters.
- Equality columns first, then range.
- One index per access pattern, not per column.
Design a rate limiter: which algorithm and where does its state live?
The strongest answer: Token bucket or sliding window per client key, with the counters in a fast shared store like Redis so every gateway instance sees the same count; say what happens when the store is down — fail open or closed — because that is the real design decision
Why it works: Right. Shared, fast state, and a stated failure mode.
- Token bucket or sliding window.
- Shared fast store for counts.
- Keyed per client.
- Decide fail-open vs fail-closed.
You need setup and guaranteed teardown around a block. What do you write?
The strongest answer: A context manager — @contextlib.contextmanager with a yield between setup and cleanup in a finally, or a class with __enter__ and __exit__
Why it works: Exactly, and the finally around the yield is what makes it hold on the error path.
- with guarantees cleanup on every exit.
- @contextmanager: setup, yield, cleanup in finally.
- Or __enter__ / __exit__ on a class.
- __exit__ can suppress an exception — usually it should not.
What is a covering index and why does it help?
The strongest answer: An index that contains every column the query needs, so the database answers from the index alone without visiting the table rows — fewer reads, often a big win for hot read queries
Why it works: Right. Index-only reads skip the table.
- Index holds all columns the query needs.
- Index-only scan, no table visit.
- Add INCLUDE columns where supported.
- Costs more index storage and writes.
3–5 years
A CPU-bound function is too slow. You add threads and nothing improves. Why?
The strongest answer: The GIL lets only one thread execute Python bytecode at a time, so threads help blocking I/O, not computation — use processes, a native library that releases the GIL, or move the work out of Python
Why it works: Exactly. Threads for waiting, processes for computing.
- The GIL serialises bytecode execution.
- Threads still win on I/O waits.
- multiprocessing for CPU-bound work.
- NumPy and friends release the GIL in C.
Two requests read a balance of 100, each subtracts 30, and the balance ends at 70. What happened and what fixes it?
The strongest answer: A lost update: both read then wrote; fix with a single UPDATE that does the arithmetic in the database (SET balance = balance - 30 WHERE balance >= 30), optimistic locking with a version column, or SELECT ... FOR UPDATE
Why it works: Right. Let the database do the arithmetic, or lock or version the row.
- Read-modify-write races.
- Atomic UPDATE with the arithmetic.
- Version column for optimistic locking.
- SELECT FOR UPDATE for pessimistic.
Your queue delivers at least once. What does that force on every consumer?
The strongest answer: Idempotency: each message carries a key and the consumer records what it has processed, so a redelivery after a crash or a slow ack is a no-op; you design the side effects to tolerate repeats rather than hoping for exactly-once
Why it works: Right. Repeats are certain; make them harmless.
- At-least-once means duplicates.
- Consumer keeps processed keys.
- Side effects tolerate repeats.
- Do not rely on exactly-once.
You will search a large space of models and features. How do you keep the holdout honest?
The strongest answer: Nest the search inside training folds and keep an outer holdout — or a pre-registered confirmation set — so the search does not touch the number I report
Why it works: Exactly. Search is data use. Nest or pre-register. Shopping the outer scores is the same old peek.
- Nest search; protect an outer look.
- Watching a set spends it.
- Do not search on all rows.
- Do not shop outer-fold winners.
How do you keep an experimentation program honest when people want wins?
The strongest answer: Pre-registration, a primary, guardrails, and a place that publishes flats — I do not fund only the tests that “worked”
Why it works: Exactly. Flats are data. Trophy walls and must-win rules are how you p-hack a culture.
- Pre-register the primary.
- Publish flats.
- Guardrail metrics.
- No must-win rule.
Transcripts of your support calls read well, but customer names, product codes and order numbers are often wrong — and those are what the downstream summary depends on.
How do you approach it?
The strongest answer: Those are speech-to-text’s known weak spots. Measure error on our own audio for those entities, not overall; add vocabulary hints where supported, correct against the catalogue and order system, improve the audio, and flag low-confidence segments
Why it works: Exactly — a good overall word error rate hides failures on the rare words that matter. Measure per entity, then constrain with the lists you already own.
- Average accuracy hides entity errors.
- Measure on your own audio, per entity.
- Constrain with known lists and hints.
- Flag low-confidence segments for review.
One synchronous database call inside an async handler makes the whole service slow. Why?
The strongest answer: A blocking call stops the event loop, so no other coroutine can run until it returns — use an async driver, or push it to a thread with asyncio.to_thread
Why it works: Right. One blocking call in an async service is an outage waiting for load.
- Blocking code freezes the whole event loop.
- Use async drivers end to end.
- asyncio.to_thread for unavoidable blocking calls.
- One offender starves every other coroutine.
Optimistic or pessimistic locking for editing a document that several people open?
The strongest answer: Optimistic — a version number checked on save — because conflicts are rare and locks held across a human's editing time would block everyone; pessimistic locking suits short, contended, automated updates
Why it works: Right. Humans hold edits for minutes; a version check on save handles the rare collision.
- Optimistic: version check on save.
- Pessimistic: lock while editing.
- Human edits: optimistic.
- Short automated updates: locks are fine.
A hot cache key expires and 5,000 requests miss at once. Design against it.
The strongest answer: Coalesce: the first miss takes a lock or a single-flight slot and rebuilds while the others wait or serve the stale value; add early refresh before expiry with jittered TTLs so keys do not expire together; the database sees one read, not thousands
Why it works: Right. Single flight, stale-while-revalidate, jitter.
- One rebuild, others wait or serve stale.
- Refresh before expiry.
- Jitter TTLs.
- DB sees one read.
You embed tickets with a model trained on your own historical tickets, including future resolutions. Risk?
The strongest answer: The embedding space can carry tomorrow’s language — I treat in-house embedders like any other fit: time-safe data only
Why it works: Exactly. Learned representations remember their training stories. Time-safety applies.
- In-house embedders need time-safe training.
- Representations can carry future language.
- Normalise ≠ de-leak.
- Dimension is not a cleanse.
Your search matches queries to documents by averaging word2vec vectors of the words. Users searching “river bank” get pages about savings accounts.
What is the limitation, and what do you change?
The strongest answer: Static embeddings give one vector per word regardless of context, so both senses of “bank” share it and averaging blurs everything; a contextual model (BERT-style or a sentence-embedding model) encodes the phrase as a whole, sense included
Why it works: Exactly — one-vector-per-word cannot represent polysemy; contextual encoders produce different vectors for the same word in different sentences.
- Static: one vector per word.
- Polysemy collapses into one point.
- Contextual encoders embed the phrase.
- Sentence-embedding models for retrieval.
Three independent API calls run one after another in an async function. How do you overlap them?
The strongest answer: await asyncio.gather(a(), b(), c()) so all three are in flight at once — and decide what return_exceptions should do, because by default the first failure propagates
Why it works: Exactly, and the exception policy is the part usually left unconsidered.
- gather runs awaitables concurrently.
- Sequential awaits are sequential.
- return_exceptions decides fail-fast or collect.
- TaskGroup gives structured cancellation.
5+ years
You are designing a Python service that fans out to many slow downstreams. What shapes the design?
The strongest answer: Async end to end so waiting costs a task rather than a thread, with bounded concurrency per downstream, timeouts on every call and a budget for the whole request
Why it works: Right, and the bound per downstream is what stops you from becoming their outage.
- Async suits many concurrent waits.
- Bound concurrency per dependency.
- Timeout every call, budget the request.
- Measure with real downstream latency.
Product wants dashboards over the transactional database. When do you say no and build a separate analytical store?
The strongest answer: When the analytical queries scan large fractions of tables, need historical snapshots or joins across services, and start competing with the product for the working set; then a warehouse fed by replication or CDC with its own model is the honest answer
Why it works: Right. Row stores serve transactions; scans and history want a columnar, separately modelled store.
- OLTP: many small transactions.
- OLAP: scans, history, cross-source joins.
- Replica for light reporting.
- Warehouse when they compete.
Design multi-tenancy for a product where some tenants are regulated.
The strongest answer: Tiered isolation: shared schema with a tenant key and row-level enforcement for most, and a separate database or cluster for tenants whose contract or regulation demands it — one code path, an isolation level chosen per tenant, with noisy-neighbour limits and per-tenant keys for encryption
Why it works: Right. One product, several isolation tiers.
- Isolation as a per-tenant tier.
- Shared with row-level enforcement.
- Dedicated where required.
- Same code path.
Three teams report three “the” model metrics. What do you standardise?
The strongest answer: The decision, the primary metric, the split rule, and who can change them — I do not standardise every notebook library
Why it works: Exactly. Shared language and change control. Local tooling can differ.
- Standardise decision, metric, split, owners.
- Let libraries differ.
- Do not become the approver of every trial.
- Do not write a shelf document.
A director points at a four percent week-on-week rise and asks whether it is real.
How do you answer without statistics jargon?
The strongest answer: Say how much this measure normally moves week to week, whether four percent sits inside that, what could produce it on its own — one large account, a short week — and the one check that would settle it
Why it works: Exactly — plain words, a range taken from the measure’s own history, and a named next step. That is what uncertainty sounds like to a non-specialist.
- Compare against normal movement.
- Name what else could explain it.
- Give a range, not a verdict.
- Say what would settle it.
What do you forbid in every readout, as a house rule?
The strongest answer: Invented percentiles, hidden n, causal verbs without a design, and appendix-only caveats — the title must survive the footnote dying
Why it works: Exactly. The honesty contract is the product. Forbidding uncertainty and flats is how you become a marketing desk.
- No invented ranks.
- n visible.
- Verbs match design.
- Caveats travel with the title.
Your worker cannot keep up with the producer and memory climbs. What is the right answer?
The strongest answer: Backpressure — a bounded queue that makes the producer wait or shed load, so the system degrades predictably instead of consuming memory until it dies
Why it works: Exactly. A bounded queue turns an outage into a slowdown you can see.
- Bounded queues create backpressure.
- Unbounded queues move the failure to memory.
- Decide explicitly: wait, shed or drop.
- Make the decision observable.
How do you model a warehouse so analysts can answer new questions without you?
The strongest answer: Facts and dimensions — a star schema: a fact table of measurable events keyed to conformed dimensions like date, customer and product — so any question is a join of the fact to dimensions the analysts already know
Why it works: Right. Conformed dimensions are what make questions composable.
- Fact table: events with measures.
- Dimensions: date, customer, product.
- Conformed dimensions across facts.
- Grain declared per fact table.
How do you limit the blast radius of a failure in a system serving millions?
The strongest answer: Cells: independent copies of the whole stack, each serving a slice of customers, with a thin routing layer; a bad deploy or a poison request takes out one cell, deploys roll cell by cell, and capacity grows by adding cells — at the cost of cross-cell features being hard
Why it works: Right. Isolation by cell; the cost is anything cross-cell.
- Independent full-stack cells.
- Customers pinned to a cell.
- Deploy cell by cell.
- Cross-cell features are the cost.
Who owns the eval suite for an assistant that can spend money?
The strongest answer: A named owner, a release gate, and a budget to refresh hard cases — not “research will add a few prompts when they can”
Why it works: Exactly. High-impact evals are operated. Gists and vendor boards are inputs, not the gate.
- Name an owner and a gate.
- Fund refresh of hard cases.
- Vendor boards are not your suite.
- Do not give the gate to marketing.
Your offline eval score rose for three releases in a row. Over the same period, escalations from the assistant to human support went up.
What is going on, and what do you do?
The strongest answer: The offline set or its grader has drifted from production. I sample escalated sessions, label them, check the grader against those labels, fold them into the set, and keep an online signal beside the offline score
Why it works: Exactly — when the two disagree, the offline eval is the instrument under suspicion. Rebuild it from the sessions where users actually failed.
- When offline and online disagree, audit offline.
- Read and label the failing sessions.
- Validate the grader against humans.
- Refresh the set; keep both signals.
A Python service does heavy numeric work per request and latency is unacceptable. What options do you weigh?
The strongest answer: Push the loop into a library that releases the GIL, move the work to a process pool or a separate service, precompute or cache, or accept that this part does not belong in Python
Why it works: Right — and knowing when to leave Python for one component is part of the answer.
- Vectorised or native code releases the GIL.
- Process pools give real parallelism.
- Precompute or cache where the input repeats.
- Some components should leave Python.