ApexSensei

Drills Triggers

Create a child record only when a field changes

Triggers · Intermediate · 18 min · Runs in your Salesforce Developer org · Apex Path

Fire an `after update` trigger that logs one Task per Contact whose `Email` changed. Compare the new value to the old value from `Trigger.oldMap` so updates to other fields don't create a Task. Build the Tasks in a list and insert them once after the loop.

Lesson

Why this matters

Without a change check, an after update trigger that creates a child record fires on every update. Edit the Last Name, change nothing at all with a re-save, run a bulk integration update — you'd get a Task every time, flooding Compliance's queue. Trigger.oldMap lets you see the previous value and act only on real changes.

The smallest working example

trigger ContactEmailLog on Contact (after update) {
    List<Task> logs = new List<Task>();
    for (Contact row : Trigger.new) {
        Contact prior = Trigger.oldMap.get(row.Id);
        if (row.Email != prior.Email) {
            logs.add(new Task(WhoId = row.Id, Subject = 'Email changed'));
        }
    }
    if (!logs.isEmpty()) {
        insert logs;
    }
}

Name the parts

  • **Trigger.new** is the list of records being saved (new values).
  • **Trigger.oldMap.get(row.Id)** is the matching record before this update.
  • **row.Email != prior.Email** is true only when the value actually changed; a same-value save stays false.
  • The list collected inside the loop plus **one insert after the loop** keeps a 200-row bulk update to one DML statement.

A common wrong version

trigger ContactEmailLog on Contact (after update) {
    for (Contact row : Trigger.new) {
        insert new Task(WhoId = row.Id, Subject = 'Email changed');
    }
}

Two problems. No comparison with oldMap, so every update creates a Task — even one that only touched Last Name. And DML inside the loop: 200 Contacts update → 200 DML statements → past the 150-DML limit.

The fix

Contact prior = Trigger.oldMap.get(row.Id);
if (row.Email != prior.Email) {
    logs.add(new Task(WhoId = row.Id, Subject = 'Email changed'));
}

And move the DML out: if (!logs.isEmpty()) insert logs; after the loop.

Now you try

Fill in the trigger body in the starter. Keep the trigger name and the after update event — the hidden tests look for them exactly.

Learn more

The challenge

Create one Task when a Contact's Email changes. Do not create a Task when any other field changes or the Email is written back to the same value.

Examples:

  • Change Email → one Task saved for that Contact
  • Change LastName but keep Email → no Task
  • Bulk update 200 Contacts with different Emails → 200 Tasks in one insert

Requirements:

  1. emailChangeCreatesTask should pass: an Email change creates one Task for that Contact.
  2. nonEmailChangeSkipsTask should pass: a LastName-only update creates no Task.
  3. sameEmailSkipsTask should pass: writing the same Email over itself is not a change.
  4. bulkUpdateCreatesOneTaskEach should pass: a 200-record bulk update creates 200 Tasks in one insert.

Starter code

trigger AS_98365D23F261_ContactEmailLog on Contact (after update) {
}

Related drills

Try this drill free

You write the Apex yourself. ApexSensei runs it and tells you what passed and what did not.

‹ Keep a Contact count on Account