# Why feature store interviews come down to point-in-time joins

Source: https://www.techinterview.org/post/3233477290/feature-store-interview-point-in-time-joins/
Updated: 2026-08-09 · techinterview.org

The model hit 0.91 AUC in the notebook and fell apart the week it shipped. Nobody touched the training code. What changed is that at training time the pipeline read each customer's `lifetime_order_count` as it looked *today*, and at serving time it read the value as it looked the millisecond a prediction fired. Those are different numbers, and the distance between them is what a feature store interview is really measuring. Interviewers at companies with a real ML platform almost never open with "tell me what a feature store is." They hand you a scenario where the training set has quietly leaked the future, and they wait to see whether you can name why before you reach for a fancier model.

## What the round usually looks like

This shows up in ML engineer and ML platform loops at places like Uber, DoorDash, Stripe, Instacart, and Netflix, anywhere a team runs models against live data instead of batch scoring once a night. It is usually a 45 to 60 minute design conversation, sometimes bolted onto a data-modeling round. You get a loose product prompt and a whiteboard, and the interviewer probes the seams. The prompt is deliberately underspecified so you have to ask what the prediction actually is and when it fires.

The questions tend to arrive phrased like a real problem someone hit last quarter, not like a textbook:

- "Walk me through how you'd build training data for a fraud model that scores every checkout."

- "A feature looks great in offline eval and hurts us in production. Where do you look first?"

- "This feature takes 400 ms to compute and the request budget is 30 ms. Now what?"

- "Two teams both need 'user spend over the last 7 days.' How do you stop them writing two slightly different versions?"

Every one of those is the same question wearing a different coat. Can you keep the number a model trains on identical to the number it sees when it runs, across time and across pipelines.

## The point-in-time join, and why it's the whole game

Start with training data, because that is where careful people still get burned. You have a table of labeled events: this checkout was fraud, that one wasn't, each stamped with the exact moment it happened. To train, you need features describing the world *as it was at that moment*, not as it is now. If your fraud label is from March 3rd at 14:22, the feature `charges_last_24h` has to reflect only charges before 14:22 on March 3rd. Pull today's value and you have handed the model information from the future, information it will never have at prediction time. The offline score looks incredible. Production is a different story.

The correct operation is a point-in-time join, sometimes written as an as-of join. For each label row, you look up each feature's value as of the label's timestamp, taking the most recent update at or before that instant, and often no fresher than some allowed cutoff to model real serving delay. A plain equality join on the entity id skips the temporal condition entirely and silently leaks. A good answer says this out loud and sketches the join condition; a great answer mentions that doing this efficiently over billions of rows is why the feature-store machinery exists at all, and why a naive correlated subquery per row will never finish.

If the interviewer pushes, talk about label leakage from the other direction too: a feature computed from data that only exists *because* the event happened, like a chargeback flag populated days after the transaction it describes. Point-in-time correctness catches the timing leak. It does not catch a feature that is a proxy for the label. You have to reason about that one yourself.

## Training-serving skew is a code problem, not a data problem

Here is the part people miss. Skew usually is not caused by bad data. It is caused by two pipelines that compute "the same" feature with two different pieces of code. Offline, you write a Spark or SQL job that aggregates a warehouse table into training features. Online, a service computes the feature at request time from whatever it can reach in a few milliseconds. Two languages, two authors, two subtly different definitions of what "last 7 days" rounds to. They drift, nobody notices, and the model degrades in a way no dashboard flags because both halves look fine on their own.

A feature store's real pitch is a single feature definition materialized to two backends: an offline store the training job reads, and an online store the serving path reads. Write the aggregation once, and both sides consume the same output. That is the property worth defending in the interview. It is also where you should be candid that the promise is only as strong as your materialization: if a bug lands between when you write the offline value and when you push it to the online store, the two can still diverge, and now you own a dual-write consistency problem instead of a dual-code one. Naming that tradeoff unprompted is a strong signal.

| Property | Offline store (training) | Online store (serving) |
| --- | --- | --- |
| Purpose | Build historical training and backfill data | Serve the latest feature value at prediction time |
| Typical backend | Warehouse or columnar files (BigQuery, Snowflake, Parquet on S3) | Low-latency key-value store (Redis, DynamoDB, Cassandra) |
| Read pattern | Large point-in-time joins over full history | Single-entity lookup by join key |
| Latency target | Minutes, inside a batch job | Single-digit to low tens of milliseconds per request |
| Freshness | As of the last materialization run | As fresh as the write path allows, up to streaming |
| What breaks it | Missing the temporal cutoff, causing future leakage | Stale or missing values when materialization lags |

## Freshness is a design choice, so make it on purpose

Once the interviewer trusts that you understand consistency, they push on freshness. Not every feature needs the same staleness budget, and pretending they do gets expensive. `average_order_value_lifetime` can be recomputed by a daily batch job and nobody cares if it is 20 hours old. `failed_payments_last_5m` is worthless if it is even a minute stale, because the fraud pattern you care about is happening right now. The first is a batch feature. The second has to come off a streaming aggregation over Kafka or Flink, written to the online store continuously.

So a real answer splits the feature set by freshness requirement and picks the cheapest path that meets each one. You do not run a streaming job for a feature that changes daily, and you do not serve yesterday's value for a feature that decides whether to block a transaction. Backfills belong in this conversation too. When you add a new feature, you have to compute its history for the training set, and that history has to obey the same point-in-time rules as everything else. Forgetting to backfill correctly is how a brand-new feature poisons an otherwise clean training run.

## Where candidates actually lose the thread

The most common miss is treating a feature store as a glorified cache. A cache stores whatever you last computed. A feature store owns the definition, the materialization schedule, the point-in-time semantics, and the guarantee that offline and online agree. If your whole design is "Redis in front of a query," you have solved the read latency and none of the correctness problems the round is about.

The opposite miss is over-building. If you have four features and one model, you do not need Tecton and a streaming stack; a well-modeled Postgres table with an explicit event-timestamp column and a disciplined join can be the right call, and saying so shows judgment. Reach for the platform when feature reuse across teams, online serving latency, and point-in-time backfills all bite at once. That is the scenario Feast covers as the open-source default, and managed options like Tecton, Hopsworks, or the Databricks Feature Store exist to sell you the operational burden you would otherwise carry yourself. Uber's Michelangelo and Airbnb's Chronon are the internal systems most of this vocabulary was borrowed from, and dropping one of those with a specific reason lands better than reciting a vendor list. For the canonical write-up of the join semantics, Feast's docs on point-in-time correctness are the cleanest short reference ([docs.feast.dev](https://docs.feast.dev)).

Watch the join key too. Features are keyed by an entity, a user, a merchant, a device, and the interviewer may quietly change which entity the prediction is about halfway through. A feature stored per user cannot answer a question asked per device without a join you now have to design. Candidates who assume one entity for the whole problem get caught when the prompt shifts under them.

If you get one thing across in this round, make it this. The model is the easy part. The number you feed it, computed the same way at 3 a.m. in a Spark job and at request time in a service that has 30 milliseconds to answer, is the part that decides whether any of the modeling mattered.
