Syllabus Lesson 79 of 156 · Privacy by design
Privacy by design

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)                        # 30

In 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.
Your turn

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:

  1. is_expired(record, today, retention): return True when the record's age in days on today is equal to or more than retention[record["kind"]].
  2. 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).
  3. untrue_claims(policy, retention): return a sorted list of the kinds in retention where the policy is untrue: the kind is missing from policy, or the policy's number of days is smaller than the real one.
Print the deleted IDs and the untrue claims. Tests use other records, dates and policies.

Spotted a problem in this lesson? Report it

Code · runs in your browser
Output