Retention and honest policies
Why it matters Data that is kept forever will eventually be part of a leak. A retention schedule, a rule for how long each kind of data lives, turns "we should clean up some day" into code that runs every night. And your privacy policy should say exactly what that code does, no more and no less.
A fridge works because things have use-by dates. Nobody debates whether to keep last month's milk; the date on the carton decides. Give every kind of data in your system a use-by date.
Retention schedule: a list of each kind of data you keep, how many days you keep it, and why. For example: server logs 14 days (to debug errors), abandoned baskets 30 days, support emails 365 days.
Delete on a schedule, and prove it
A small job runs every night: for each record, work out its age, and delete it once the age reaches the limit for its kind. "Prove it" means keeping a deletion log of which records went and when, but not their contents, so you can show the job really runs.
from datetime import date
created = date.fromisoformat("2026-09-01")
today = date.fromisoformat("2026-10-01")
age_days = (today - created).days # subtracting dates gives a timedelta
print(age_days) # 30In this course a record is expired when its age in days is equal to or more than the limit. So with a 30-day limit, a basket made on 1 September is deleted on 1 October.
A function can hand back two results at once by returning a tuple: return kept, deleted_ids. The caller unpacks it with kept, deleted_ids = apply_retention(...).
A policy that tells the truth
A privacy policy is a promise. It is true only if the code keeps it. For retention, check each kind of data:
- If the policy does not mention a kind you keep, it is untrue by omission.
- If the policy says "30 days" but the code keeps it for 90, it is untrue.
- If the policy says "at most 90 days" and the code deletes at 30, it is still true: you keep it no longer than you said.
Plain sentences beat legal-sounding ones. Compare:
Vague: "We retain data for as long as necessary for business purposes."
Honest: "We keep server logs for 14 days to fix errors, then delete them.
We keep abandoned baskets for 30 days, then delete them."A common misconception: "deleting from the main database is enough". Copies hide in backups, log files, analytics exports and email inboxes. A truthful schedule covers them too, for example "backups roll over after 35 days".
In other languages
# Django: a nightly management command
cutoff = timezone.now() - timedelta(days=30)
deleted, _ = Basket.objects.filter(created__lt=cutoff, paid=False).delete()-- SQL (PostgreSQL), run from a scheduled job
DELETE FROM server_logs WHERE created_at < now() - interval '14 days';// JavaScript
const ageDays = Math.floor((today - created) / 86_400_000); // ms in a day// Go
ageDays := int(today.Sub(created).Hours() / 24)Recap
- Give every kind of data a number of days, and delete automatically when it is reached.
- Log which records were deleted, not what they held, so you can prove the job runs.
- A policy is true only if every kind you keep is listed, for no less time than you really keep it.
ShopD keeps the records in RECORDS; each is a dict with "id", "kind" and "created" (text like "2026-09-01"). RETENTION says how many days each kind is kept, and every record's kind is in it. POLICY is what the privacy policy currently claims. Write three functions:
is_expired(record, today, retention): returnTruewhen the record's age in days ontodayis equal to or more thanretention[record["kind"]].apply_retention(records, today, retention): return a tuple(kept, deleted_ids): the records that are not expired (same order), and the list of the expired records'"id"s (same order).untrue_claims(policy, retention): return a sorted list of the kinds inretentionwhere the policy is untrue: the kind is missing frompolicy, or the policy's number of days is smaller than the real one.
This lesson is locked
Lessons open one at a time. Finish the previous lesson to unlock this one.