What Makes a Dataset Leak-Proof: A Checklist Before You Train
Data leakage is the quiet reason models look brilliant in the notebook and mediocre in production. Here is how I check a dataset before I let anything near a training loop.
Why leakage is the most expensive mistake you will not notice
Most model failures announce themselves. The loss does not converge, the predictions are obviously wrong, someone complains. Leakage is different. It makes everything look better than it should, right up until the model meets real data it was never secretly briefed about. I have come to treat high validation accuracy with the same suspicion a careful reader treats a headline that confirms exactly what they wanted to hear. If the number is too good, the first question is not 'how do we deploy this' but 'what did the model see that it should not have'.
Leakage happens when information from outside the legitimate training signal sneaks into the features or the split, usually information that encodes the answer or a proxy for it. It is rarely dramatic. It is a timestamp column, a duplicate row, a normalisation step computed on the whole dataset before splitting. Each of these feels harmless in isolation. Together they produce a model that has memorised shortcuts rather than learned structure, and a leaderboard score that collapses the moment the data distribution shifts even slightly.
The reason this matters practically, not just academically, is cost. A leaky pipeline can run for weeks before anyone notices the production metric diverging from the validation metric. By then there are stakeholders who have already made decisions based on the inflated number. Catching leakage before training is cheap. Catching it after deployment is expensive and embarrassing in roughly equal measure.
A worked example: the timestamp that gave everything away
Suppose we are building a model to predict whether a customer will cancel a subscription within the next thirty days, using a table of account activity. The features include things like login frequency, support tickets raised, and a column called last_contact_date, which records the most recent time a human agent spoke to the customer. The target is churn within thirty days of some reference point.
Here is the quiet problem. In many real systems, a customer only gets a retention call from support once churn risk has already been flagged internally. So last_contact_date being recent is often a symptom of the company already suspecting the customer is about to leave, which happens after some internal signal fires, a signal that is correlated with the very churn we are trying to predict. If this feature is included naively, the model does not learn the drivers of churn; it learns to detect the company's own early-warning system. Validation accuracy might sit around ninety-two percent. In production, where that internal flag has not fired yet for new customers, accuracy drops into the seventies, because the model's best feature was effectively a mirror of the label with a short delay.
A second, subtler version of the same problem: if you split your data randomly by row rather than by customer, you might have activity from the same customer's account appearing in both the training set and the validation set, just from different weeks. The model does not need to generalise to new customers at all; it just needs to recognise account identifiers it has already memorised. Validation accuracy looks excellent because the model is effectively doing lookup, not prediction. Neither of these problems shows up as an error message. They show up as a gap between validation performance and reality, and by the time you measure that gap you have already shipped something.

The practical checklist
Before training anything, I now walk through a short list of questions. None of them are exotic, but skipping any one of them has caused real damage in projects I have seen or read about.
- Split by the right unit, not by row. If your data has a natural entity such as a customer, patient, or device, split by that entity, not by individual observations. Otherwise rows from the same entity bleed across train and validation, and the model learns identity rather than pattern.
- Respect time. If the task is predictive and temporal, train on the past and validate on the future, never the reverse. Shuffling time series data before splitting is one of the most common ways to manufacture false confidence.
- Fit preprocessing only on the training fold. Scalers, imputers, encoders, and feature selectors should be fitted on training data alone, then applied unchanged to validation and test data. Fitting a scaler on the full dataset before splitting leaks summary statistics from the held-out set into training.
- Interrogate every feature's timing. For each column, ask: at the moment of prediction in a real deployment, would this value actually be available? If a feature is only populated after the outcome is already known or strongly implied, it is leakage dressed up as a predictor.
- Check for duplicate or near-duplicate rows. Near-duplicates across splits, common in scraped text or image datasets, let the model recognise rather than generalise. A simple hash or similarity check before splitting catches most of this.
- Audit target-derived features. Aggregated statistics like group means or counts that include the current row's own label, even indirectly, leak the answer into the feature. Compute such aggregates with the current row excluded, and recompute them per fold.
- Be suspicious of suspiciously good results. If accuracy, AUC, or any metric is dramatically higher than comparable published baselines on similar tasks, treat that as a prompt to investigate rather than a result to celebrate.
None of these checks require sophisticated tooling. Most can be done with a notebook, a groupby, and a healthy dose of scepticism about your own pipeline.
Takeaway
Leak-proofing a dataset is not a one-off audit you perform once and forget; it is a discipline you apply every time the data or the pipeline changes. The checklist above is deliberately boring, because the failures it guards against are boring too: a column that should not exist yet, a split that groups the wrong thing, a scaler fitted too early. None of it is clever. All of it is necessary.
The practical habit I would recommend is to treat every unusually strong result as a question rather than an answer. Before celebrating a high score, trace each feature back to the moment it would actually be available in production, and trace each split back to whether it truly separates unseen entities from seen ones. If both of those hold up under scrutiny, the number in front of you has a decent chance of meaning something. If they do not, you have saved yourself from building confidently on top of a mirage.
