Free lesson 1: Privacy Engineering Foundations: GDPR, nDSG and a Worked Feature Review

Privacy engineering turns data protection law into features, schemas, pipelines and defaults that behave the way the privacy notice promises. This lesson maps the field, translates the EU GDPR and the Swiss nDSG into engineering requirements, and walks through a complete review of one small app feature – data flow, legal basis, risks and measures.
- 12lessons in the course
- 7GDPR principles turned into engineering questions
- 1feature reviewed end to end in this lesson
- ~22 minreading time for this lesson
This course explains data protection law from an engineer's perspective so you can build and discuss systems competently. It is not legal advice. Laws, guidance and court decisions change, and the right answer often depends on facts this lesson cannot know. Where a decision has legal consequences – a legal basis, a transfer abroad, a breach notification – check with your data protection officer or legal counsel. Examples use fictional companies, reserved domains such as example.com and documentation IP addresses.
What privacy engineering is – and how it differs from security
Personal data is any information relating to an identified or identifiable natural person. "Identifiable" is the key word: a device ID, an IP address or a location trail can identify someone without a name. Processing is almost anything you do with it – collect, store, log, send to a vendor, train a model, delete.
Security asks: can an attacker get at the data? Privacy asks more: should this data exist, should we use it this way, and does the person know? A perfectly secured database is still a privacy failure if it holds data nobody needed or keeps it forever. Privacy protects people from the organisation as well as from attackers.
- "The bucket is encrypted, so we are fine."
- Protect everything we collect, collect everything we can.
- Access control stops outsiders; insiders may query anything.
- Encryption is one control; purpose and minimisation come first.
- Collect what the stated purpose needs – and nothing else.
- Access follows purpose: support sees tickets, not location history.
Who does what: controller, processor, data subject
The controller decides why and how personal data is processed – usually the company whose product you build. A processor processes data on the controller's behalf and instructions: the cloud host, the email service, the crash reporter. The data subject is the person the data is about. Roles follow facts, not contract titles; Lesson 3 goes deeper.
Two laws, one engineering job: GDPR and the Swiss nDSG at a glance
The GDPR applies to organisations established in the EU and to organisations elsewhere that offer goods or services to people in the EU or monitor their behaviour there. The revised Swiss Federal Act on Data Protection (nDSG, revDSG or FADP) has applied since 1 September 2023 and covers processing with an effect in Switzerland, even when it starts abroad. A Swiss app with users in Germany usually has to think about both.
The two laws share most principles, and for an engineer most of the day-to-day work is identical: know your data, have a purpose, minimise, secure, delete on time, respect people's rights. The differences sit mainly in the legal mechanics. The table is a simplified overview – precise conditions are a matter for counsel.
| Topic | GDPR (EU) | nDSG (Switzerland) |
|---|---|---|
| Who is protected | Natural persons | Natural persons (legal entities are no longer covered) |
| Starting point | Processing needs one of six legal bases (Art. 6), such as consent, contract or legitimate interests | Private processing is generally permitted if it follows the principles; a justification (consent, overriding interest or law) is needed where it unlawfully infringes personality, for example by breaching the principles |
| Sensitive data | "Special categories" (Art. 9), e.g. health, biometrics for identification, religious beliefs; criminal data handled separately | "Sensitive personal data", a similar list that also includes administrative and criminal proceedings and social assistance measures |
| Privacy by design and by default | Art. 25 | Art. 7 |
| Record of processing activities | Art. 30, with a limited exemption for small organisations | Art. 12; small companies can be exempt under conditions set out in the ordinance – check with counsel |
| Impact assessment | DPIA when processing is likely to result in high risk (Art. 35) | Data protection impact assessment when high risk is likely (Art. 22) |
| Breach notification to the authority | Without undue delay and, where feasible, within 72 hours, unless the breach is unlikely to result in a risk | To the FDPIC as soon as possible when the breach is likely to result in a high risk |
| Enforcement | Supervisory authorities can impose administrative fines on organisations | The FDPIC investigates and issues binding orders; criminal fines can be imposed on responsible individuals for certain intentional violations |
"Swiss law is more relaxed" is a dangerous shortcut. Duties to inform, secure, delete and answer access requests are real under both laws, and if you serve EU users the GDPR applies to that processing anyway. Design for the stricter common denominator.
The mental model: principles as engineering requirements
Article 5 of the GDPR lists the principles everything else builds on; the nDSG has closely related ones in Article 6. Engineers should read them as requirements that can be designed, tested and reviewed.
| Principle | The engineering question | Typical control |
|---|---|---|
| Lawfulness, fairness, transparency | Can we name the legal basis, and would a user be surprised by this flow? | Purpose and basis recorded per data flow; just-in-time notice in the UI |
| Purpose limitation | Is this data reused for something we never announced? | Purpose tags on tables and events; review gate for new uses |
| Data minimisation | Could the feature work with less, coarser or shorter-lived data? | Optional fields removed, coarse location, aggregated metrics |
| Accuracy | Can users correct wrong data, and do downstream copies update? | Edit flows, change propagation to replicas and vendors |
| Storage limitation | When exactly is each record deleted – including backups and logs? | TTL columns, scheduled purge jobs, log retention settings |
| Integrity and confidentiality | Who and what can read this data, and would we notice misuse? | Encryption, least privilege, access logging, alerts on bulk export |
| Accountability | Could we prove all of the above to an auditor next week? | Record of processing, DPIAs, decision logs, tested deletion |
Privacy by design means these questions are answered while the feature is designed, not after it ships. Privacy by default means the out-of-the-box setting processes only what is necessary: sharing starts off, profiles start private, retention starts short.
- Purposewhy the feature needs data at all
- Datathe minimum set, at the coarsest useful precision
- Basiswhich legal ground carries this flow
- Riskwhat could harm the person
- Controlsdesign, defaults, security, retention
- Proofrecords a reviewer can check
Worked example: a privacy review of a "Nearby running partners" feature
Here is a realistic design review, worked from start to finish. The company is fictional; the method is the one you will reuse throughout the course.
Example Sports AG, based in Basel, builds a running app with users in Switzerland, Germany and France. The product manager proposes "Nearby running partners": runners nearby can see each other's first name, typical pace and preferred running times, and send a request to run together. The first draft of the ticket says: "Share the user's GPS position every minute, show the exact distance to other runners, use birth date for an age filter and heart-rate history to match fitness levels." You are asked to review it before development starts.
- Pin down the purpose. One sentence a user would recognise: "Help runners who opted in find people with a similar pace who run nearby at similar times." Note what is not the purpose: advertising, movement profiles, selling insights.
- Draw the data flow. Every hop: device, API, matching service, push provider, the other phone, logs, analytics – and which systems belong to vendors (Figure 2).
- Inventory each data item. Needed for the purpose? At what precision? For how long? Who sees it?
- Choose and record the legal basis with counsel, check the nDSG principles, and plan how users are informed.
- List the risks to people – being located, followed, profiled or exposed – not risks to the company.
- Design measures and defaults: less data, coarser data, shorter retention, opt-in, rate limits, access rules.
- Write it down in a short review record linked to the ticket.
Step 3 in detail: the data inventory
| Data item in the draft | Needed for the purpose? | Decision |
|---|---|---|
| GPS position every minute | Only "roughly where" is needed | Snap to a 1 km cell on the device; send only when the user opens the feature; keep the cell for 24 hours |
| Exact distance in metres | No – a band is enough to decide | Show bands: "within 2 km", "2–5 km"; never a map pin |
| Birth date for an age filter | An age band is enough | Store an age band derived at sign-up; feature limited to adults |
| Heart-rate history | No – self-declared pace matches just as well | Dropped. Heart rate is health data and would make the feature far riskier under both laws |
| First name and photo | Yes, to recognise each other | First name or nickname; photo optional |
| Pace band and time slots | Yes, core of the matching | Stored in the profile; editable; deleted with the profile |
| Chat messages after a match | Yes, to arrange the run | Kept while both users keep the match; deleted 90 days after unmatch; block and report available |
The best measures cost nothing: dropping heart-rate matching removed a whole category of sensitive data. Minimisation is the cheapest control – if it happens before the schema exists.
Step 4: legal basis and transparency – the same feature through three lenses
Every flow needs a legal basis. The feature is optional and makes users visible to strangers, so in this scenario counsel decides to switch it on only through a separate, explicit opt-in that also serves as consent – informed, specific, freely given and as easy to withdraw as to give. Withdrawal deletes the stored cell; whether "contract" could also carry the matching is for counsel. The operating system's location prompt is a technical permission, not legal consent.
For a private company the nDSG does not require a legal basis for every step; it requires that the principles are respected, with a justification where personality would otherwise be infringed. Users are informed at collection, and privacy by default (Art. 7) means the feature starts off. Because positions of private individuals become visible to strangers, the team treats the risk as potentially high and documents an impact assessment.
Transparency is a UI problem. The opt-in screen says in three lines what others will see (first name, pace, "within 2 km"), what they will not see (exact position, routes) and how to switch it off. An indicator shows while you are visible.
Steps 5 and 6: risks to people and the measures that answer them
| Risk to the runner | Before review | Measure | After |
|---|---|---|---|
| R1 Someone works out where you live from repeated early-morning positions | High | 1 km cell, sent only on demand, kept 24 h, no history | Low |
| R2 Exact distance lets a stalker narrow down your position by moving around | High | Distance bands, server-side rate limits on searches, no map pin | Low |
| R3 Minors become discoverable by adults | High | Feature limited to adult accounts; block and report | Medium |
| R4 Marketing later reuses positions for ads | Medium | Purpose tag "partner-matching" on the store; reuse requires a new review | Low |
| R5 Push vendor learns who wants to run with whom | Medium | Generic notification text; content loaded inside the app | Low |
| R6 Data survives after the user opts out | Medium | Opt-out deletes cell and visibility at once; tested in CI | Low |
| R7 Coordinates and emails end up in logs | Medium | Redaction filter at source; 14-day log retention | Low |
R3 stays medium on purpose: self-declared ages are imperfect, and an honest residual risk beats a table full of "Low".
Step 7: what you should end up with
The output is not a long document but a short, versioned record next to the code:
# privacy/reviews/nearby-partners.yaml (example review record)
feature: nearby-running-partners
purpose: "Help opted-in runners find people with similar pace nearby"
not_purposes: [advertising, movement profiles, resale]
data:
- item: location_cell # 1 km grid cell, computed on the device
retention: 24h
visible_to: [matching-service]
- item: pace_band
retention: until profile deleted
visible_to: [matching-service, matched-users]
dropped: [precise_gps, exact_distance, birth_date, heart_rate_history]
legal_basis:
gdpr: "explicit opt-in / consent (counsel decision 2026-10-12)"
ndsg: "principles respected; information at collection; default off"
processors: [push-provider] # generic payload only
residual_risks:
- id: R3
level: medium
reason: "self-declared age"
dpia: required # positions visible to strangers
owner: product-privacy@example.com
This file feeds the record of processing (Lesson 3), the impact assessment (Lesson 4) and the purge jobs (Lesson 9).
Under the hood: why coarse location is harder than it looks
"Round the coordinates" sounds trivial. It is not. One degree of latitude is about 111 km everywhere, but longitude shrinks with the cosine of the latitude: at Basel, 0.01° of longitude is about 0.75 km, 0.01° of latitude about 1.1 km. A cleaner approach snaps positions to roughly equal-sized cells and sends only the cell centre:
import math
def snap(lat, lon, cell_km=1.0):
"""Return the centre of the ~cell_km grid cell that contains (lat, lon)."""
dlat = cell_km / 111.32 # ~111.32 km per degree of latitude
c_lat = (math.floor(lat / dlat) + 0.5) * dlat # snap latitude first
dlon = cell_km / (111.32 * math.cos(math.radians(c_lat))) # use the CELL latitude
c_lon = (math.floor(lon / dlon) + 0.5) * dlon
return round(c_lat, 4), round(c_lon, 4)
for p in [(47.5584, 7.5733), (47.5590, 7.5700), (47.5640, 7.5733)]:
print(p, "->", snap(*p))
Output (computed with Python 3):
(47.5584, 7.5733) -> (47.5611, 7.568)
(47.559, 7.57) -> (47.5611, 7.568)
(47.564, 7.5733) -> (47.5611, 7.568)
Three positions up to about 600 metres apart become the same cell – which is the point. Two subtle bugs matter. Computing the longitude step from the user's latitude instead of the cell's makes the "coarse" value vary slightly with the exact position, quietly leaking detail. And a cell only hides a position if it is snapped on the device; a precise value sent "to be snapped on the server" already sits in request logs and proxies.
If the app showed exact distances, someone could move around and query repeatedly to narrow down another person's position. The defence is design: distance bands computed from cell centres, limits on how often a user may move and search, and alerts on accounts that query from many cells quickly. Lesson 5 turns these into a reusable pattern catalogue.
A second, deeper example: personal data in logs and analytics
The first example was a new feature. The second is existing plumbing – where most unexpected personal data lives. Logs collect emails, IPs and tokens because developers log whole requests; analytics events carry user IDs that are joined back to accounts. Both are usually kept far longer than anyone decided.
During the running-app review you open the log platform and find lines like login ok user=anna@example.com ip=192.0.2.147 url=/sync?token=eyJhbGciOi.abc123, kept for 13 months. Analytics events contain the raw email as user identifier. Your task: stop new personal data at the source and make the analytics identifier useless outside the analytics system.
Part 1 – redact at the source, not in the log platform
Redaction belongs in the application, before a line is written; otherwise every later system – shippers, vendor storage, backups – holds the raw values. Python's logging filters can rewrite a record first:
import logging, re
EMAIL = re.compile(r"[\w.+-]+@[\w-]+\.[\w.]+")
IPV4 = re.compile(r"\b(\d{1,3}\.\d{1,3}\.\d{1,3})\.\d{1,3}\b")
TOKEN = re.compile(r"(token=)[A-Za-z0-9._-]+")
class RedactFilter(logging.Filter):
def filter(self, record):
msg = record.getMessage() # merge args into the message first
msg = EMAIL.sub("[email]", msg)
msg = IPV4.sub(r"\1.0", msg) # keep the /24 network, drop the host part
msg = TOKEN.sub(r"\1[redacted]", msg)
record.msg, record.args = msg, None
return True
handler = logging.StreamHandler()
handler.setFormatter(logging.Formatter("%(levelname)s %(message)s"))
handler.addFilter(RedactFilter())
log = logging.getLogger("api")
log.addHandler(handler)
log.setLevel(logging.INFO)
log.info("login ok user=%s ip=%s url=/sync?token=%s",
"anna@example.com", "192.0.2.147", "eyJhbGciOi.abc123")
Expected output (Python 3):
INFO login ok user=[email] ip=192.0.2.0 url=/sync?token=[redacted]
The filter calls getMessage() first, so values passed as arguments are redacted too; it keeps only the network part of the IP, often enough for abuse detection; and it removes the token, which is also a credential. Pattern redaction is a safety net, not a design – the real fix is to stop logging whole URLs and user objects.
Part 2 – pseudonymise the analytics identifier properly
Replacing the email with a hash looks like an easy win. It is a trap: a plain hash is deterministic, so anyone with a list of candidate emails can hash and compare. A keyed hash (HMAC) needs a secret key to compute or test values.
import hashlib, hmac
email = "anna@example.com".strip().lower()
# 1) Plain hash: anyone who can guess the input can recompute it
plain = hashlib.sha256(email.encode()).hexdigest()
print("sha256 :", plain[:16])
# 2) Keyed hash (HMAC): without the key nobody can recompute or test guesses
KEY = b"demo-key-load-me-from-a-secrets-manager"
pseudo = hmac.new(KEY, email.encode(), hashlib.sha256).hexdigest()
print("hmac :", pseudo[:16])
Output (first 16 hex characters, computed with Python 3):
sha256 : f7d54c22dda5b178
hmac : 0cbd45a07a9ff210
Anyone can reproduce the first value from the address alone; the second needs the key. Keep the key in a secrets manager outside the analytics environment and plan rotation – a new key breaks linkability with old data.
Be precise: the data is now pseudonymised, not anonymous. The company holds the key, so under both laws it remains personal data. Pseudonymisation reduces risk; it does not take data out of scope. Lesson 6 covers techniques that approach real anonymisation and their limits.
What went wrong if you see this
The log still shows the email after the filter was added
The filter sits on the wrong handler, or a library logs through its own handler. Attach it to every output handler and add a unit test that logs a test address and fails if it appears.
The same user gets different pseudonyms on iOS and the web
Inputs are not normalised: "Anna@Example.com " and "anna@example.com" differ. Normalise first, and compute the pseudonym on the server so the key never ships inside an app.
Seeing the effect: the risk picture before and after the review
A risk matrix shows stakeholders what a review achieved: likelihood from left to right, severity for the person from bottom to top.
Most measures move a risk to the left – less likely – while the harm to someone who is actually stalked stays the same. Only minimisation (coarser or removed data) moves risks downward. That is why reviewers ask "do we need this data?" before "how do we protect it?".
Common mistakes in privacy engineering – and better habits
The same misconceptions appear in almost every team that starts taking privacy seriously. Two deserve a sentence each. "We hashed it, so it is anonymous" is wrong: hashed, encrypted or tokenised data that anyone can link back is still personal data. "Consent solves it" is wrong too: consent is one legal basis among several, it can be withdrawn at any time – so the system must really stop – and it never cures collecting data the feature does not need.
- Review the specification, never the actual traffic
- Delete from the main database and call it done
- Collect now, decide the purpose later
- One privacy notice written once and never updated
- Treat every feature with the same heavy process
- Inspect what the app and SDKs really send in a test environment
- Trace deletion through replicas, caches, vendors, logs and backups
- No field without a purpose and a retention period
- Notice and record of processing updated in the same pull request
- Short triage for everything, deep review only where risk is real
Troubleshooting: symptoms, causes and fixes
Typical findings when a team first tests its privacy promises:
| Symptom | Likely cause | Fix |
|---|---|---|
| A deleted user still receives the newsletter | A copy of the address lives at the email vendor and nobody propagates deletions | Deletion event fans out to every processor listed in the data map; verify with a test account |
| An erased account reappears after a database restore | Backups are restored without replaying deletions made since the backup | Keep a deletion log (IDs only) and re-apply it after every restore |
| Analytics shows events from users who declined tracking | The SDK is initialised at app start, before the consent decision | Initialise only after consent; test by inspecting outgoing requests on a fresh install |
| An access request export misses support tickets | The ticket system is not in the data map | Add every system holding personal data to the inventory; generate exports from the map |
| A "anonymised" dataset lets colleagues recognise people | Rare combinations of attributes – postcode, age, job title – single individuals out | Generalise or aggregate further, suppress small groups, re-test before release |
How experienced privacy engineers work
Professionals triage fast, go deep only where people could be harmed, and verify the running system rather than the design document:
- Intake – about 15 minutesNew personal data? New purpose? Sensitive data or children? New vendor or country? Visible to others? All no: record the triage and move on.
- Design review – one to two hoursThe seven steps of the worked example.
- Impact assessment – when risk is highNecessity, risks, measures, residual risk, sign-off (Lesson 4).
- Build – every pull requestSchema changes carry purpose and retention; tests check redaction, deletion and consent gating.
- Pre-launch – half a dayInspect what really leaves the device and backend; compare with the review record and notice.
- After launch – recurringProve purge jobs ran, run a test access request, re-review when purpose or vendors change.
Thinking errors of advanced practitioners
Experienced people trust the diagram over the traffic (three services on the slide, seven third-party hosts in the capture), gold-plate until teams route around review, stop at the database while copies live in the warehouse and backups, and argue law instead of design when removing the data would make the question disappear.
The defender's view: privacy controls that also stop attackers
Every record you do not collect cannot leak, and every purpose-based access rule also limits a compromised account.
- Purpose-based access: support sees ticket fields through a view, not raw tables.
- Access logging: record who read which customer record and why; alert when one account exports 50,000 profiles at 03:00.
- Canary records: a fictional customer no legitimate process touches – any read is a strong signal.
- Breach readiness: the GDPR's 72-hour window (where feasible) and the nDSG's "as soon as possible" assume you can quickly tell whose data was affected – only a current data map makes that possible.
A minimal example of purpose-based access in PostgreSQL – the support role gets a narrow view instead of the table:
-- Support may see contact details, never location or chat content
CREATE VIEW support_customer AS
SELECT id, first_name, email, created_at
FROM customers;
REVOKE ALL ON customers FROM support_role;
GRANT SELECT ON support_customer TO support_role;
Test it: as support_role, SELECT * FROM customers; should fail with "permission denied", while the view returns four columns. Lesson 10 adds encryption, key separation and breach response.
Check yourself: privacy engineering basics
1. Analytics events store a plain SHA-256 hash of each email. How is this data classified?
- Anonymous, outside the law
- Personal data – the hash is at most a pseudonym
- Always sensitive data
- Not personal, because no name is stored
Show answer
B – Anyone with candidate emails can recompute the hash, and the company can link it back.
2. Which decision reduced the feature's risk the most?
- Encrypting the matching database
- A longer privacy notice
- Snapping the position to a 1 km cell on the device
- Accepting the terms again
Show answer
C – Minimisation at the source protects every downstream system at once.
3. Under the GDPR, a breach is generally notified to the authority…
- within 24 hours in every case
- only above 1,000 affected people
- without undue delay and, where feasible, within 72 hours, unless unlikely to result in a risk
- only after the investigation ends
Show answer
C – Art. 33. Under the nDSG: the FDPIC, as soon as possible, when a high risk is likely.
4. What does privacy by default mean for the feature?
- On for everyone, with an off switch
- Off at first; only necessary data until the user chooses more
- Default passwords must change
- Encryption by default
Show answer
B – Defaults process only what the purpose needs; visibility is opt-in.
5. Which statement about the nDSG is accurate?
- It protects natural persons and companies
- It requires one of six legal bases, like the GDPR
- It protects natural persons; private processing is generally permitted if the principles are respected
- It ignores processing that starts abroad
Show answer
C – A justification is needed where personality is unlawfully infringed; effects in Switzerland count even from abroad.
6. A log filter only rewrites record.msg. Why do emails still appear?
- Records cannot be changed
- Logging arguments are merged later, so the filter never sees them
- Regex cannot match emails
- The log level is too low
Show answer
B – Call getMessage() first, redact, then clear record.args.
You can now…
- Define personal data – and why "identifiable" matters more than "named".
- Assign roles – controller, processor, data subject.
- Compare GDPR and nDSG at overview level and know what to ask counsel.
- Translate the seven principles into engineering controls.
- Run a seven-step feature review and write the record.
- Coarsen location correctly – on the device, with distance bands.
- Redact logs at the source, including arguments and tokens.
- Prefer a keyed hash – and know it is still personal data.
- Triage features with five intake questions.
Glossary
For the primary sources, the Swiss act is published on Fedlex, and the FDPIC website publishes guidance for Swiss organisations.
What's next in this course
- Lesson 2 – GDPR and Swiss nDSG for Engineers: principles, legal bases and the differences that change a design.
- Lesson 3 – Data Mapping, Roles and the Record of Processing: build an inventory that stays current with the code.
- Lesson 4 – Privacy Risk Analysis and the DPIA in Practice: when an assessment is needed and how to run one.
- Lesson 5 – Privacy by Design & by Default: minimisation patterns for schemas, APIs and mobile apps.
- Lesson 6 – Pseudonymisation and Anonymisation: tokenisation, aggregation, k-anonymity, noise – and their limits.
- Lesson 7 – Consent, Cookies, SDKs and Analytics: consent that the code really enforces.
- Lesson 8 – Data Subject Rights as Engineering: access, erasure and portability as reliable pipelines.
- Lesson 9 – Retention, Deletion and Privacy-Safe Logging: purge jobs, backups and log hygiene.
- Lesson 10 – Security of Processing and Breach Response: encryption, access control and notification decisions.
- Lesson 11 – Processors, International Transfers and AI Systems: vendors, third countries and machine learning data.
- Lesson 12 – Capstone & Next Steps: a full privacy review of a fictional product, plus 20 practice questions.
Lessons 2–12 and the final course test unlock with a one-time purchase – no subscription.