Washout / Clean / Lookback Period
A pre-index window of continuous, observable history used to define incident (new) use or incident disease, to measure baseline covariates, and to align time zero, by requiring the absence of the qualifying exposure/event and the presence of data coverage before the index date.
On this page
A clean lookback period is a stretch of time before a patient's first fill of a study drug during which the analyst confirms the patient had no prior fills of that drug — proving they are a brand-new starter, not someone already taking it. The analyst also checks that the patient was continuously enrolled in their insurance plan across that entire stretch, because a gap in coverage means a missing fill would be invisible, not a true absence. One honest limit: it only works with data sources that record every dispensing, so cash-paid fills or fills at out-of-network pharmacies can slip through undetected.
A washout (clean) period and a lookback (baseline/assessment) window are two uses of the same pre-index history, and conflating them is a common protocol error. The washout is an exclusionary window: a patient qualifies as a new (incident) user only if there is no fill of the study drug (and, in an active-comparator design, no fill of the comparator) during the window, so that follow-up starts at first exposure (time zero) for everyone.
The lookback is a measurement window: the same (or a different) span over which baseline covariates, comorbidities, prior treatment, and incident-disease status are ascertained. Both require the patient to be observable — continuously enrolled with the relevant benefit (medical + pharmacy in claims; in-system contact in EHR) — across the entire window, otherwise "no prior fill" or "no prior diagnosis" is missingness, not a true clean period.
A washout is only as long as the data are observable, which is why washout length, lookback length, and the continuous-enrollment requirement are jointly specified, not chosen independently.
Core conceptual distinction
three decisions are bundled and must be separated. (1) Washout (clean) period — defines incidence by requiring absence: no qualifying drug/diagnosis in the window. Longer washouts more reliably exclude prevalent users (who carry depletion-of-susceptibles and survivor bias) but shrink the cohort and, in finite-data sources, force exclusion of recent enrollees.
(2) Lookback (covariate-assessment) window — defines what is measured about baseline. A fixed window (e.g., 365 days) is comparable across patients but can miss a true chronic condition coded only once years ago; an all-available lookback captures more confounders but introduces differential covariate ascertainment because people with longer observable history accrue more codes (Brunelli 2013; Nakasian 2017).
(3) Continuous-enrollment / observability — guarantees the washout and lookback reflect real absence, not unobserved care. The estimand is unchanged by these choices, but the population and the measured confounder set are not: a 90-day washout and a 730-day washout answer the same question on materially different cohorts.
Pros, cons, and trade-offs
(named against the alternatives):
- Longer vs shorter washout (e.g., 365 vs 180 vs 90 days): A longer washout more completely removes prevalent users and undercounts incident events less (a short window misclassifies long-quiescent chronic conditions as "incident," exaggerating risk — e.g., short atrial-fibrillation lookbacks inflate baseline stroke risk). Cost: it excludes anyone without that much continuous coverage, biasing toward stably enrolled (often older, commercially insured or continuously Medicare) patients and reducing power. Prefer the longest washout the data reliably support, and report cohort yield at each candidate length.
- Fixed vs all-available lookback for covariates: A fixed window gives comparable ascertainment across patients; all-available data captures more true confounders and usually reduces residual confounding for stable chronic conditions, but inflates apparent prevalence in longer-enrolled patients and can introduce surveillance/immortal-time artifacts if exposure-related encounters generate the codes (Brunelli 2013; Nakasian 2017; Preen 2006). Prefer a fixed lookback for the primary analysis with an all-available sensitivity analysis, and never let a covariate be measured after time zero.
- vs no/implicit washout (prevalent or ever-user cohorts): Specifying a clean period removes immortal time, depletion of susceptibles, and adjustment for post-initiation mediators. Cost: a smaller, initiator-skewed cohort (see `prevalent-user-bias`, `new-user-design`).
When NOT to use — and when it is actively misleading or dangerous
- Washout longer than reliably observable history. If the database has only 12 months of pre-index data for most patients, a 24-month washout silently selects the minority with long enrollment — a selected, healthier-coverage population. Always tabulate how many patients each window length retains.
- Treating absence-of-observation as a clean period. In Medicare Advantage (encounter, not FFS, claims) or during a gap in pharmacy benefit, "no prior fill" is missingness. Restricting to MA-only person-time to satisfy a washout is dangerous: the washout is unfalsifiable. Require A/B/D (or commercial medical+pharmacy) coverage across the window.
- Short washout on chronic, intermittently treated conditions. A 90-day clean period will reclassify long-standing AF, diabetes, or heart failure as "incident," producing spuriously high early event rates and exaggerated risk (Czwikla 2017).
- Asymmetric or post-index covariate windows. Measuring a covariate using any data after time zero, or using a longer lookback in one arm than the other, conditions on a mediator or creates differential misclassification — a self-inflicted bias.
- All-available lookback when encounters are exposure-driven. If the act of initiating treatment generates the work-up that records the confounder, all-available ascertainment manufactures imbalance and immortal time.
Data-source operational depth
(claims vs EHR vs registry vs linked):
- Claims (FFS): The washout is implemented as "no NDC/J-code for the drug class and no qualifying diagnosis in the [index_date − washout, index_date) window," gated on continuous medical + pharmacy enrollment over the entire window. Failure modes: (a) MA-only person-time lacks complete FFS claims — encounter data are incomplete and under-captured, so a washout computed on MA-only spans is unverifiable; restrict to FFS Parts A/B/D (or commercial medical+Rx). (b) Claims adjudication lag and reversals — a fill reversed after submission can leave a phantom "prior fill" that wrongly disqualifies a true new user; use paid, non-reversed claims. (c) Left truncation — the first observable date is enrollment start, not birth/disease onset; a washout cannot see exposure before coverage. (d) 90-day mail-order and stockpiling distort where the last pre-index `days_supply` ends and whether the washout is truly drug-free. (e) Differential competing risks — in elderly claims, death/disenrollment differs by exposure, so a covariate measured over an all-available lookback is differentially observed by arm.
- EHR: Capture is encounter-driven, so a "clean" lookback reflects in-system contact, not the patient's true history; external care leakage (a fill or diagnosis at another system) breaks the washout. Prefer linkage to pharmacy claims to confirm true non-use; define an explicit minimum-contact rule (e.g., ≥1 encounter per lookback year) to operationalize observability, and treat patients who leave the system as informatively censored.
- Registry: Strong for adjudicated incident-disease definitions and severity but typically weak for complete pharmacy exposure; link to claims to verify the drug washout and to a death index to firm up censoring.
- Linked claims–EHR–vital records: The ideal substrate (EHR severity + claims completeness + mortality) but linkage selects the linkable subset and creates order/fill/service date discrepancies that must be reconciled before the washout and time-zero are assigned.
Worked claims example
Question: incident heart-failure hospitalization among new users of a second-generation sulfonylurea, in a commercial + Medicare-FFS database.
- Observability gate: require 365 days of continuous medical and pharmacy enrollment with no MA-only person-time immediately before the candidate index.
- Drug washout: the candidate index is the first sulfonylurea fill (`fill_date`); the patient qualifies as a new user only if there is no sulfonylurea NDC in [index_date − 365, index_date). A fill on day −400 is fine (outside the window); a reversed fill on day −200 must be dropped so it does not falsely disqualify.
- Incident-disease washout (outcome side): to study first HF, require no HF diagnosis (inpatient or ≥2 outpatient) in the same 365-day clean window, because a 90-day window would label long-standing HF as incident and inflate early rates (Czwikla 2017).
- Lookback for covariates: measure comorbidities, prior insulin, HbA1c proxies, and utilization over [index_date − 365, index_date] only — never using any claim dated ≥ index_date — to feed a high-dimensional propensity score.
- Sensitivity: re-run with 180-day and all-available washout/lookback, reporting cohort yield at each (e.g., 365d retains 41,200; 730d retains 22,900 and shifts older), and verify covariate prevalence is not driven by enrollment length. This grid — not a single number — is the defensible output.
Decision diagram
flowchart TD
Pop[Candidate: first fill of study class] --> Obs{Continuous med+Rx coverage<br/>over full window? No MA-only?}
Obs -->|No| ExObs[Exclude: washout unobservable<br/>absence = missingness]
Obs -->|Yes| DrugW{Prior study-class fill in<br/>washout-start .. index?}
DrugW -->|Yes| ExPrev[Exclude: prevalent user]
DrugW -->|No| DisW{Prevalent disease in window?<br/>IP once OR OP >=2}
DisW -->|Yes| ExDis[Exclude: prevalent disease]
DisW -->|No| Cohort[New user at time zero = index_date]
Cohort --> Look[Measure covariates only in<br/>baseline_start .. index_date]
Look --> Sens[Sensitivity: vary washout & lookback length,<br/>fixed vs all-available; report cohort yield]gantt title Washout and lookback windows for one candidate initiator (claims) dateFormat YYYY-MM-DD axisFormat %b %Y section Observability Continuous med+Rx enrollment (no MA-only) :done, enr, 2022-09-01, 2024-01-01 section Clean window Drug + disease washout (no study fill, no prevalent disease) :active, wash, 2023-01-01, 2023-12-31 section Covariate lookback Fixed covariate-assessment window :crit, look, 2023-01-01, 2023-12-31 section Time zero First study-class fill = index_date :milestone, t0, 2024-01-01, 0d
Worked example
Scenario
A pharmacist fills metformin for patient 2041 on 2023-10-15. Before counting this person as a brand-new metformin starter, the analyst looks back 180 days — from 2023-04-18 through 2023-10-14 — to confirm there were no earlier metformin fills in that window. The patient's insurance ran continuously from 2023-01-01, so there are no coverage gaps to worry about. After confirming the clean lookback, the analyst marks 2023-10-15 as the index date and then tracks the patient forward for 365 days of follow-up ending 2024-10-14.
Dataset
Pharmacy claims rows for patient 2041. The analyst sees every fill in the database; the task is to confirm the 180-day window before 2023-10-15 is empty of metformin.
| person_id | fill_date | drug | days_supply |
|---|---|---|---|
| 2041 | 2023-10-15 | metformin | 90 |
| 2041 | 2024-01-13 | metformin | 90 |
| 2041 | 2024-04-13 | metformin | 90 |
Steps
Result
- Label
Lookback = 180 clean days (2023-04-18 to 2023-10-14); patient qualifies as a new metformin user; index date = 2023-10-15
- Value
180
Trade-offs
Runnable example
New-user cohort construction with a drug washout, a continuous-enrollment observability gate, and a fixed covariate lookback. Required inputs (already cleaned, de-duplicated, paid+non-reversed claims only): rx : pharmacy fills -> person_id, fill_date (datetime64), ndc, drug_class, days_supply dx : diagnoses ->...
import pandas as pd
WASHOUT_DAYS = 365 # drug-free + disease-free clean period that defines incident use
LOOKBACK_DAYS = 365 # fixed covariate-assessment window (== washout here; can differ)
STUDY_CLASS = "SULFONYLUREA"
DISEASE_CODES = ("I50",) # ICD-10 heart failure prefix for the incident-disease washout
def build_new_user_cohort(rx: pd.DataFrame, dx: pd.DataFrame, enroll: pd.DataFrame) -> pd.DataFrame:
rx = rx.sort_values(["person_id", "fill_date"])
study = rx[rx["drug_class"] == STUDY_CLASS]
# Candidate index = first observed fill of the study class.
idx = (study.groupby("person_id", as_index=False)
.agg(index_date=("fill_date", "min")))
idx["baseline_start"] = idx["index_date"] - pd.Timedelta(days=LOOKBACK_DAYS)
idx["washout_start"] = idx["index_date"] - pd.Timedelta(days=WASHOUT_DAYS)
# Observability gate: continuous medical+pharmacy coverage, no MA-only span, across the FULL washout->index window.
e = enroll.merge(idx[["person_id", "index_date", "washout_start"]], on="person_id")
covered = e[(e["med_rx_covered"]) & (~e["ma_only"]) &
(e["enroll_start"] <= e["washout_start"]) &
(e["enroll_end"] >= e["index_date"])]
idx = idx[idx["person_id"].isin(covered["person_id"])].copy()
# Drug washout: drop anyone with a prior study-class fill inside [washout_start, index_date).
prior_rx = study.merge(idx[["person_id", "index_date", "washout_start"]], on="person_id")
prior_rx = prior_rx[(prior_rx["fill_date"] >= prior_rx["washout_start"]) &
(prior_rx["fill_date"] < prior_rx["index_date"])]
idx = idx[~idx["person_id"].isin(prior_rx["person_id"])].copy()
# Incident-disease washout: drop prevalent disease (IP once OR OP >=2) inside the clean window.
d = dx.merge(idx[["person_id", "index_date", "washout_start"]], on="person_id")
d = d[d["dx_code"].str.startswith(DISEASE_CODES) &
(d["dx_date"] >= d["washout_start"]) & (d["dx_date"] < d["index_date"])]
ip = d.loc[d["care_setting"] == "IP", "person_id"]
op2 = d[d["care_setting"] == "OP"].groupby("person_id").size()
prevalent = set(ip) | set(op2[op2 >= 2].index)
idx = idx[~idx["person_id"].isin(prevalent)].copy()
return idx[["person_id", "index_date", "baseline_start"]].reset_index(drop=True)New-user cohort construction with data.table mirroring the Python logic. Inputs: rx : person_id, fill_date (Date), ndc, drug_class, days_supply dx : person_id, dx_date (Date), dx_code, care_setting ('IP'/'OP') enroll : person_id, enroll_start, enroll_end, med_rx_covered (logical), ma_only (logical) Returns one row...
library(data.table)
WASHOUT_DAYS <- 365L
LOOKBACK_DAYS <- 365L
STUDY_CLASS <- "SULFONYLUREA"
DISEASE_RX <- "^I50" # heart-failure ICD-10 prefix
build_new_user_cohort <- function(rx, dx, enroll) {
setDT(rx); setDT(dx); setDT(enroll)
study <- rx[drug_class == STUDY_CLASS]
setorder(study, person_id, fill_date)
idx <- study[, .(index_date = fill_date[1L]), by = person_id]
idx[, baseline_start := index_date - LOOKBACK_DAYS]
idx[, washout_start := index_date - WASHOUT_DAYS]
# Observability gate: continuous med+rx coverage, no MA-only, spanning the full washout window through index.
e <- merge(enroll, idx[, .(person_id, index_date, washout_start)], by = "person_id")
covered <- e[med_rx_covered == TRUE & ma_only == FALSE &
enroll_start <= washout_start & enroll_end >= index_date, unique(person_id)]
idx <- idx[person_id %chin% covered]
# Drug washout: exclude any prior study-class fill inside the clean window.
pr <- merge(study, idx[, .(person_id, index_date, washout_start)], by = "person_id")
prior_ids <- unique(pr[fill_date >= washout_start & fill_date < index_date, person_id])
idx <- idx[!person_id %chin% prior_ids]
# Incident-disease washout: exclude prevalent disease (IP once OR OP >= 2) inside the clean window.
d <- merge(dx, idx[, .(person_id, index_date, washout_start)], by = "person_id")
d <- d[grepl(DISEASE_RX, dx_code) & dx_date >= washout_start & dx_date < index_date]
ip <- unique(d[care_setting == "IP", person_id])
op2 <- d[care_setting == "OP", .N, by = person_id][N >= 2L, person_id]
idx <- idx[!person_id %chin% union(ip, op2)]
idx[, .(person_id, index_date, baseline_start)]
}New-user cohort construction in SAS using PROC SQL set logic. Required input datasets (post data-management; paid, non-reversed claims only): work.rx : person_id, fill_date, ndc, drug_class, days_supply work.dx : person_id, dx_date, dx_code, care_setting ('IP'/'OP') work.enroll : person_id, enroll_start, enroll_end,...
%let washout = 365;
%let lookback = 365;
%let studyclass = SULFONYLUREA;
/* Candidate index = first study-class fill. */
proc sql;
create table idx as
select person_id,
min(fill_date) as index_date format=date9.,
min(fill_date) - &lookback as baseline_start format=date9.,
min(fill_date) - &washout as washout_start format=date9.
from work.rx
where drug_class = "&studyclass"
group by person_id;
quit;
/* Observability gate, drug washout, and incident-disease washout in one filtered pass. */
proc sql;
create table cohort as
select i.person_id, i.index_date, i.baseline_start
from idx i
/* continuous med+rx coverage, no MA-only, across the full washout window through index */
where exists (
select 1 from work.enroll e
where e.person_id = i.person_id and e.med_rx_covered = 1 and e.ma_only = 0
and e.enroll_start <= i.washout_start and e.enroll_end >= i.index_date)
/* no prior study-class fill inside the clean window (=> incident user) */
and not exists (
select 1 from work.rx p
where p.person_id = i.person_id and p.drug_class = "&studyclass"
and p.fill_date >= i.washout_start and p.fill_date < i.index_date)
/* no inpatient disease code inside the clean window */
and not exists (
select 1 from work.dx d
where d.person_id = i.person_id and d.care_setting = 'IP'
and d.dx_code like 'I50%'
and d.dx_date >= i.washout_start and d.dx_date < i.index_date)
/* not >=2 outpatient disease codes inside the clean window */
and i.person_id not in (
select d.person_id from work.dx d
where d.care_setting = 'OP' and d.dx_code like 'I50%'
and d.dx_date >= i.washout_start and d.dx_date < i.index_date
group by d.person_id having count(*) >= 2);
quit;Citations
- [1]Ray WA. Evaluating medication effects outside of clinical trials: new-user designs. American Journal of Epidemiology. 2003;158(9):915-920.
- [2]Czwikla J, Jobski K, Schink T. The impact of the lookback period and definition of confirmatory events on the identification of incident cancer cases in administrative data. BMC Medical Research Methodology. 2017;17:122.
- [3]Brunelli SM, Gagne JJ, Huybrechts KF, et al. Estimation using all available covariate information versus a fixed look-back window for dichotomous covariates. Pharmacoepidemiology and Drug Safety. 2013;22(5):542-550.
- [4]Nakasian SS, Rassen JA, Franklin JM. Effects of expanding the look-back period to all available data in the assessment of covariates. Pharmacoepidemiology and Drug Safety. 2017;26(8):890-899.