ML Systems Lab Open interactive version →
Intermediate 50 min read train-test splitcross-validationdata leakagetemporal leakageoverfitting

Data Splits and Leakage

Understand why models that look great in development fail in production — and the exact mistakes that cause it.

You are predicting hospital readmission from records of 5,000 patients — and some patients appear many times, once per visit. You do a normal random 80/20 split, train, and get 99% validation accuracy. You ship it. Production accuracy: 73%. Nothing errored. Where did 26 points go?

Here is the leak. Patient 147 has nine visits in your data. The random split scattered seven of them into training and two into validation. So during training the model memorised patient 147 — their exact labs, age, history — and when it "predicted" their two validation visits, it was not generalising to a new patient at all; it was recognising someone it had already studied. Your validation score was partly measuring *memorisation*, and memorisation does not exist in production, where every patient is new. This is leakage: information from the evaluation set sneaking into training. It fires no error, the metrics look great, and the model fails on real data. It comes in four flavours.


The same entity on both sides

That was patient 147: related rows (same patient, user, or household) split across train and test, so the test set is not truly independent — this is group leakage. The fix is a group split — every row from a given patient goes entirely to one side, so validation always contains patients the model has never seen.


Training on the future

With time-ordered data, a random split lets the model train on March to predict January — the reverse of reality, where you never have tomorrow's data today. That's temporal leakage. The fix is a time cutoff: train on everything before a date, validate on everything after.


The quiet one

Fit a scaler (or imputer) on all 5,000 records *before* splitting, and its mean and spread were computed partly from the validation rows — so the training transform is tainted by the data you are supposed to be judging on. This is preprocessing leakage. Target encoding leaks worse, and differently. A target encoder replaces each category with the *mean of the label* for that category — fit it on all 5,000 records before splitting, and every validation row's encoded value was computed using the labels of the very validation rows it will later be graded on. That is not a distributional statistic sneaking across the boundary like a scaler's mean and spread; it is the label itself, smuggled into a feature column before training even starts. Fit every transformer — scaler, imputer, or target encoder — on the *training* data only, then apply it to validation. A pipeline enforces this so you do not have to remember.


The answer hiding in a column

A feature like "rehospitalised_within_30_days" on a readmission-prediction row *is the label wearing a disguise* — it could only be known after the outcome. This is feature leakage: any feature that needs knowledge of the future is unavailable at prediction time. The classic tell: a single new feature makes accuracy jump 15 points. Real features never do that.


The through-line: a held-out test set alone does not save you. If a feature was built using future information — even for rows that ended up in *training* — the model learned a pattern that will not exist once it is deployed. Real protection is structural: split *before* fitting anything, judge only on data the model never touched, and check every feature for whether you would actually have it at prediction time.


Duplicates and near-duplicates leak too

Group leakage's cousin: exact or near-duplicate rows split across train and test. The same product listing scraped twice, the same image at two resolutions, an augmented copy of a training example, the same document under two IDs — any of these landing on both sides means the model is tested on something it effectively trained on. Deduplicate (and near-deduplicate on a similarity key) *before* splitting, and make sure augmentation happens *after* the split so an original and its augmentations never straddle the boundary.


The target-leakage taxonomy is bigger than one column

"A feature that is the label in disguise" comes in several forms worth naming: post-outcome features (computed after the event), label-derived features (a transform of the target), proxy labels (an innocent column that's a near-perfect stand-in, like a case-ID range that encodes the outcome), aggregation-window leakage (a rolling stat whose window reaches past the prediction time), and future-window leakage (any feature summarising events after T). The audit question is always the same — *would this exact value exist at prediction time, knowing nothing about the future?* — but the disguises are many.


Test-set reuse and nested CV

Leakage isn't only about features — it's also about *decisions*. Every time you tune against the same validation/test set — trying architectures, thresholds, feature sets and keeping whatever scores best — you leak your own choices into it, and the reported number drifts optimistic (you've overfit to the test set through the back door). Guard it: touch the test set once, at the very end. And when you tune hyperparameters or features aggressively, use nested cross-validation — an outer loop for the honest estimate, an inner loop for all the tuning — so the reported score reflects data the selection never saw.


Time-series CV: windows, gaps, backtesting

A single time cutoff is the start; robust temporal validation has more structure. Expanding-window CV trains on all history to date and tests the next slice (more data, stable relationships); sliding-window trains on a fixed recent span (better under drift). Add a gap/embargo between train and test so rolling-window features can't bleed across the boundary. And backtest across multiple cutoffs rather than one — a strategy that works at one date and fails at three others isn't real. One split is an anecdote; a backtest is evidence.


Match the split to the production question

The split should mirror what you'll actually predict. Deploying to new users? Split by user (group split). Predicting new sessions for existing users? Split by session. Scoring new transactions, new products, or future events? Split accordingly — by transaction, by product, or temporally. The distinction between entity-level and event-level deployment decides the split: if production always sees brand-new entities, your validation must too, or your number answers a question you'll never be asked.


Feature selection leaks, and features must survive to serving

Two final structural traps. Selection leakage: correlation filtering, mutual-information ranking, PCA, RFE, and any *target-based* feature selection must happen inside the CV folds — pick features using the full data before splitting and you've let the test set influence which features exist. And production parity: every feature you keep must be *computable at prediction time* with the same freshness, latency, and timestamp constraints as offline — a feature that's trivial to compute over historical tables but unavailable (or stale) in the real-time path is a leak that only surfaces after deployment. Feature selection itself — which method to use, wrapper versus filter tradeoffs, and stability across resamples — is deep enough to earn its own treatment: that's exactly where the next module, Feature Selection, picks up.

Key points

Takeaway

Leakage fires no error and produces no warning — the model trains cleanly, metrics are excellent, and the system ships. It fails when real data arrives. The only protection is structural: enforce the split before any transformer is fit, audit every feature for temporal validity, and never reuse the test set.

Recap

Check your understanding

Q1. You build a model to predict customer churn. You include 'support_tickets_after_churn_date' as a feature. The model achieves 98% accuracy. What is the problem?

Q2. Why does random train-test splitting fail for time-series forecasting, and what is the correct splitting strategy?

Q3. You run 5-fold cross-validation on a medical imaging dataset with 500 patients and 20 images per patient. You get CV accuracy of 94%. You deploy and get 71%. What happened?

Q4. List three preprocessing operations that can cause leakage when applied before the train-test split.

Q5. What is the correct order of operations for preprocessing inside a k-fold cross-validation loop?

Q6. You aggressively tune hyperparameters and select features by repeatedly checking performance on the same held-out set, then report that set's score as your final number. Which TWO of the following are true?

Q7. You augment your image training set (rotations, crops) and also deduplicate, but do both before the train/validation split. Why can this still leak, and what's the correct order?

Try it interactively

ML Systems Lab is a free interview-prep platform for ML engineers — work through the full interactive module, quizzes, and drills.

Open ML Systems Lab →