ML Feature Pipelines and Feature Stores Questions
Data infrastructure for machine learning: feature pipelines, feature stores, online/offline consistency, training-serving skew, and data preparation for models. Covers building reliable feature platforms and preventing leakage in the data path feeding models. The data-engineering-for-ML topic.
Design an incremental feature generator (pseudocode is acceptable) that consumes a stream of events and maintains per-user online features: a running count, an approximate unique-item count, last-seen timestamp, and time-since-last-event. Explain how you would persist and checkpoint the running state between runs, how you would handle exactly-once versus at-least-once ingestion semantics, and how you would recover from a crash without double-counting.
Sample Answer
Direct answer: An incremental online feature generator maintains a small piece of running state per user (count, an approximate distinct-count sketch, last-seen timestamp) that updates with each new event, persists that state periodically so a crash does not lose it, and uses an idempotent update rule keyed by event ID so replayed events under at-least-once delivery do not double-count.
Structured elaboration:
- State design. Per user:
count(a plain integer),unique_count(a HyperLogLog or similar sketch for approximate distinct counting, since exact distinct counts do not update incrementally without unbounded memory),last_seen(a timestamp, simply overwritten by the newest event), andtime_since_last_event(derived at read time asnow - last_seen, not stored directly). - Update logic. For each incoming event: increment
count, add the relevant field to theunique_countsketch, and setlast_seento the event's timestamp if it is newer than the current value (to correctly handle slightly out-of-order arrival without regressinglast_seen). - Persistence and checkpointing. Periodically (e.g. every N events or every few seconds) snapshot the in-memory state to a durable store (a local state backend like RocksDB, checkpointed to object storage, which is exactly how Flink and Spark Structured Streaming manage state); on restart, load the last checkpoint and resume rather than starting from zero.
- Exactly-once vs. at-least-once handling. Under at-least-once delivery, the same event can be redelivered after a crash-and-restart even if it was already applied. To avoid double-counting, track the last-processed event ID (or offset) per user as part of the checkpointed state, and skip any incoming event whose ID/offset is not newer than what was already applied for that user, making the update idempotent with respect to redelivery.
- Crash recovery without double-counting. On restart, resume from the last checkpoint, resume consuming from the source at the last committed offset, and apply the idempotency check above so any events reprocessed between the checkpoint and the crash point are correctly skipped rather than re-applied.
class IncrementalUserFeatures:
def __init__(self):
self.count = 0
self.unique_ids = set() # stand-in for a HyperLogLog sketch in this simplified version
self.last_seen = None
self.last_processed_offset = -1
def apply_event(self, offset, event_id, timestamp, unique_field):
if offset <= self.last_processed_offset:
return # idempotent skip: already applied (redelivery)
self.count += 1
self.unique_ids.add(unique_field)
if self.last_seen is None or timestamp > self.last_seen:
self.last_seen = timestamp
self.last_processed_offset = offset
def snapshot(self):
return {
"count": self.count,
"unique_count": len(self.unique_ids),
"last_seen": self.last_seen,
"last_processed_offset": self.last_processed_offset,
}
Worked example (verified by execution): Applying events at offsets 0, 1, 2 updates count to 3; if the process crashes after offset 2 but before the next checkpoint, and the source redelivers offsets 1 and 2 on restart, the idempotency check (offset <= last_processed_offset) correctly skips both, leaving count at 3, not 5. Running this against a small scripted sequence (apply 0,1,2; simulate crash; reapply 1,2) confirms count == 3 and last_processed_offset == 2 after recovery, exactly as intended.
Trade-offs & pitfalls: Using a plain set() for unique counting is exact but unbounded in memory, which does not scale to millions of users with high-cardinality fields; a real implementation needs a probabilistic sketch (HyperLogLog) that trades a small, bounded error (typically under 2%) for constant memory per user. The offset-based idempotency check assumes offsets are strictly increasing per user's event stream and that the source guarantees at-least-once (not out-of-order arbitrary) delivery; if events can arrive wildly out of order (not just redelivered), a simple "skip if not newer" rule can incorrectly skip a legitimately new event that happens to have a lower offset due to multi-partition interleaving, which needs a per-partition or per-event-ID (not per-offset) deduplication key instead.
Design an access-control model for a feature registry: read-only discovery for most users, write and modify permissions for feature owners, and export permissions for a compliance team. Describe the policy enforcement points, audit logging, and how you would implement row- and column-level restrictions for PII-sensitive features.
Sample Answer
Direct answer: A feature-registry access model needs three tiers of permission (broad read-only discovery, narrower write access scoped to feature owners, and a separate export permission for compliance) enforced through policy checks at every API call, with row- and column-level restrictions applied specifically to PII (personally identifiable information)-sensitive features so discovery does not itself leak sensitive values.
Structured elaboration:
- Read-only discovery (broad). Most users can browse the feature catalog: names, descriptions, non-sensitive metadata (owner, freshness, data type), and, for non-PII features, sample or full values. This maximizes reuse and avoids duplicated feature engineering, which is a primary value of having a shared registry at all.
- Write/modify permissions (feature owners). Only the declared owner (or their team) can modify a feature's definition, transformation logic, or metadata; a change request from outside the owning team goes through a review/approval workflow rather than direct write access, preventing an uncoordinated change from breaking consumers who depend on the feature's current behavior.
- Export permissions (compliance). A separate, more restrictive permission gates bulk export or download of feature data (as opposed to querying it through normal serving/training paths), since export is the highest-risk action for data exfiltration; this permission is typically granted narrowly (a compliance or data-governance team) and logged with extra scrutiny.
- Policy enforcement points. Enforce these checks at the API gateway or service layer for every request (list features, read a feature's metadata, read a feature's value, modify a definition, export), not just at the UI level, since a UI-only check is trivially bypassed by calling the underlying API directly.
- Audit logging. Every discovery, read, write, and export action is logged with the requesting principal, the resource, and the outcome (allowed/denied), providing the trail needed for both security investigation and compliance reporting.
- Row- and column-level restrictions for PII. At the column level, a PII-tagged field (raw email, precise location) is masked or excluded entirely from the discovery/read response unless the requester holds the specific elevated permission for that field, independent of their broader feature-registry role. At the row level (less common but relevant for regulated data, e.g. EU-resident user data under GDPR (General Data Protection Regulation)), restrict which specific entities' feature rows a given role or region-scoped service can query at all, not just which columns.
Worked example: A data scientist browsing the catalog sees that a user_risk_score feature exists, its owner, and its freshness SLA (service-level agreement) (discovery, broadly available); they can query its value for model training (read, available to their role); but a request to export the full historical table of user_risk_score values for 10 million users is denied unless they hold the separate export permission, and the denied attempt is logged, prompting (if legitimate) a request through the compliance team's approval process rather than a silent failure with no path forward.
Trade-offs & pitfalls: Making column-level PII masking the DEFAULT (deny unless explicitly granted) rather than opt-out is the safer design but adds friction for legitimate use cases; teams often under-invest in a smooth elevated-access request process, which then incentivizes workarounds (asking a teammate with broader access to run the query "for them") that undermine the access model entirely. Row-level restrictions add real query complexity and performance overhead (every query needs an additional filter based on the requester's scope) that is easy to get wrong or forget to apply consistently across every access path (the registry's own query API might enforce it correctly while a debugging/admin tool bypasses it, which is a common gap found in real audits).
Design an end-to-end CI/CD and testing strategy for feature pipelines. Cover unit tests, integration tests, contract and schema tests, data diffs, canary rollouts for new feature computations, automated backfill validation, and the gates that must pass before a feature change reaches production.
Sample Answer
Direct answer: A CI/CD strategy for feature pipelines needs tests at multiple levels (unit tests for individual transforms, integration tests against realistic data, contract and schema tests that catch breaking changes early) plus deployment mechanics (canary rollouts, data diffs, and automated backfill validation) so a bad feature change is caught before, not after, it reaches production models.
Structured elaboration:
- Unit tests. Test individual transformation functions in isolation with small, hand-crafted inputs and known expected outputs, covering edge cases (nulls, empty inputs, boundary values) the way any well-tested code would be, run on every commit as the fastest feedback loop.
- Integration tests. Run the full pipeline (or a meaningful slice of it) against a realistic sample dataset (production-like, ideally a sampled and PII (personally identifiable information)-scrubbed slice of real data) to catch issues that only appear when components interact, such as a join that behaves differently at realistic data volume or with realistic key distributions.
- Contract and schema tests. Validate that a feature's output schema (types, nullability, value ranges) matches its declared contract before deployment, catching a breaking change (a column type change, a newly-nullable field) at CI time rather than when it silently breaks a downstream consumer in production.
- Data diffs. Before promoting a changed feature-computation job, run both the old and new logic against the same input and diff the outputs; a large, unexpected diff (more rows changed than the intended fix should cause) is a strong signal something is wrong, and this check catches classes of bugs that unit and integration tests, which only check known cases, would miss.
- Canary rollouts for features. Deploy a changed feature computation to a small percentage of traffic or to a shadow/parallel path first, comparing its output against the existing production version before fully cutting over, so a subtle bug affects a small blast radius and is caught by automated comparison rather than a full-scale incident.
- Automated backfill validation. When a feature change requires backfilling historical data, validate the backfilled values against expected statistical properties (row counts, null rates, value distributions matching expectations) before the backfill is considered complete and the new values are trusted.
- Deployment gates. Require all of the above to pass, plus perhaps a required review for schema-breaking changes specifically, before a feature-pipeline change reaches production; a failed data diff or schema-contract check should block the deploy automatically, not just warn.
Worked example: A change to a "days since last purchase" feature's calculation logic (fixing a timezone bug) would run: unit tests confirming the fix against hand-crafted timezone-boundary cases; an integration test against a sampled production dataset; a data diff comparing old vs. new logic across that same sample, expecting changes concentrated near timezone-boundary timestamps and flagging the deploy for review if the diff shows unexpectedly widespread changes; and a canary rollout materializing the new logic for 5% of entities and comparing model performance metrics on that slice before rolling out to 100%.
Trade-offs & pitfalls: Data diffs and canary rollouts add real latency to the deployment process (a canary needs to run long enough to gather a meaningful comparison), which is a genuine trade-off against shipping fixes quickly; the mitigation is scaling the rigor to the risk (a typo fix in a log message needs less ceremony than a change to a core feature's transformation logic feeding dozens of models). Contract/schema tests are only as good as the contracts being enforced; a pipeline that never formally declared a schema contract for a feature has nothing for the CI check to validate against, so establishing contracts is a prerequisite this strategy assumes, not something it can retrofit automatically.
Explain how watermarking choices trade off completeness against latency when handling late-arriving events. Contrast an aggressive watermark policy with a conservative one, and describe the practical consequences for emitted aggregates and storage.
Sample Answer
Direct answer: An aggressive watermark (a short allowed-lateness) closes windows and emits results quickly, minimizing latency but dropping or mishandling more genuinely late-arriving events; a conservative watermark (a longer allowed-lateness) waits longer before closing a window, capturing more late data at the cost of higher latency and more state held in memory.
Structured elaboration: The watermark is max_observed_event_time - allowed_lateness. Setting allowed-lateness to, say, 5 seconds means the window closes and emits almost immediately once the wall clock catches up, which is ideal for a low-latency online feature but means any event delayed by more than 5 seconds is either dropped or must be handled as a separate late-correction path. Setting it to 10 minutes means the window waits substantially longer before finalizing, capturing the vast majority of realistically-delayed events, but the feature is correspondingly less fresh, and the engine has to keep 10 minutes of window state open rather than a few seconds.
Worked example in Spark Structured Streaming: with .withWatermark("event_time", "10 minutes") on a 1-minute tumbling window, a window covering 10:00-10:01 will not emit its final result until the watermark passes 10:01, which happens once the max observed event time reaches roughly 10:11 (10 minutes of allowed lateness past the window end); an event for that window arriving after the watermark has already passed it is dropped from that aggregate (Structured Streaming's default behavior) unless the job is also configured to emit and merge late updates. Contrast that with a 10-second watermark on the same window: it closes and emits by roughly 10:01:10, over 100x faster, but discards essentially any event more than 10 seconds late.
Trade-offs & pitfalls: The biggest pitfall is picking a single watermark setting for a pipeline without measuring the actual lateness distribution of the source data; a watermark set shorter than the p99 lateness of real traffic silently and systematically drops a meaningful fraction of late data every window, which is easy to miss unless the pipeline monitors dropped-late-event counts explicitly. Conversely, an overly conservative watermark on a low-latency use case (fraud scoring needing sub-second freshness) can make the feature too stale to be useful even though it is more "complete," so the right choice depends on which side of the latency/completeness trade-off the specific feature's consumer actually needs, and different features in the same pipeline may legitimately need different watermark settings.
Explain how you would implement and test idempotent writes from a stream-processing job into an external key-value online store that does not support transactions. Provide at least two robust design patterns and describe the test cases you would use to validate correctness under retries and duplicate delivery.
Sample Answer
Direct answer: Without native transaction support, idempotency has to be engineered into the write itself: either a deterministic upsert keyed by a stable identifier so retries produce the identical end state, or a versioned write with a monotonic check so an out-of-order or duplicate write cannot regress a newer value.
Structured elaboration:
- Pattern 1: Deterministic upsert keyed by a stable ID. Every write includes a stable key (the entity ID plus, if relevant, a window or event identifier) and the write operation is a
SET key = computed_valuerather than an increment or append. Because the computed value is a pure function of the input event(s), applying the same write twice (due to retry) produces the exact same end state; no accumulation error occurs. This works well when the feature value at a point in time is fully determined by the inputs, not by "add to whatever is currently there." - Pattern 2: Versioned write with a monotonic guard. Each write carries a version or timestamp, and the store only applies the write if the incoming version is newer than what is currently stored (a conditional/compare-and-set write, which most KV (key-value) stores support even without full transactions). This handles the case where writes might arrive out of order (not just duplicated), since a stale retried write with an old version is correctly rejected rather than overwriting a newer value that already landed.
- Test cases to validate correctness:
- Simple retry: apply the same write twice in a row; assert the store's final value matches what a single application would produce.
- Out-of-order retry: apply write version 5, then a retried (duplicate) write version 4; assert the store still shows the effect of version 5, not version 4 overwriting it.
- Concurrent writers: two processes attempt the conditional write for the same key with different versions at nearly the same time; assert only the higher version's write ultimately wins, regardless of which one physically executes last (this specifically exercises the compare-and-set logic under a race, not just sequential retries).
- Crash mid-write: simulate a crash after the write is sent but before the acknowledgment is received by the writer, then have the writer retry; assert the retried write is correctly either a no-op (if the first write actually succeeded) or successfully applies (if it did not), with no partial or corrupted state either way.
Worked example: For pattern 2 in a Redis-like store, the write is implemented as a Lua script (executed atomically by Redis) that reads the current stored version, compares it to the incoming version, and only performs the SET if the incoming version is greater; this gives compare-and-set semantics without needing a full multi-key transaction, since Redis executes the whole script atomically per key.
Trade-offs & pitfalls: Pattern 1 (deterministic upsert) does not work for features that are genuinely incremental in nature (a running count computed as current + delta rather than a full recomputation); forcing such a feature into a deterministic-upsert shape often means recomputing the full value from source data on every write, which can be significantly more expensive than a true increment. Pattern 2's monotonic guard requires every writer to correctly generate a version that reflects true ordering (a wall-clock timestamp is vulnerable to clock skew across producers; a logical/vector clock is more robust but adds complexity), and a bug that assigns a lower version to a genuinely newer write silently and permanently loses that write's effect with no error raised, since the store's compare-and-set logic is working exactly as designed.
Unlock Full Question Bank
Get access to all 39 ML Feature Pipelines and Feature Stores interview questions and detailed answers.
Sign in to ContinueJoin thousands of developers preparing for their dream job.