Apex Trigger

Easy Apex Trigger Practice Problems

43 free Salesforce Apex challenges · Full problem statements, best-practice notes, and step-by-step hints · Solve live on ApexArena

This guide walks through 43 beginner-friendly Apex trigger exercises — field updates, simple validation, and basic before/after insert & update logic. Each entry below includes the full problem statement, the governor-limit and best-practice constraints it's testing, and a numbered approach to solving it — then you can open the same problem in ApexArena's browser-based Apex editor and get instant pass/fail feedback against real test cases.

On this page
  1. 1. Bulk Account Rating Updater
  2. 2. Opportunity Close Date Validator
  3. 3. Employee Salary Validator
  4. 4. Account Website Normalizer
  5. 5. Opportunity Amount Validator
  6. 6. Case Priority Auto-Setter
  7. 7. Lead Score Auto-Assign Trigger
  8. 8. Opportunity Amount Validation Trigger
  9. 9. Prevent Deletion of Won Opportunities
  10. 10. Stamp Created-By Full Name on Record
  11. 11. Lead Default Field Setter
  12. 12. Auto-Create Contact for IT Industry Accounts
  13. 13. Opportunity Probability Setter
  14. 14. Trigger: Prevent Past Close Date on Opportunity
  15. 15. Trigger: Set Revenue Tier on Account
  16. 16. Apex Trigger: Validate Contact Email Domain Before Save
  17. 17. Trigger + Handler: Enforce Account Rating Based on Annual Revenue
  18. 18. Apex Trigger: Prevent Deletion of Closed Opportunities
  19. 19. Trigger + Handler: Default Lead Source to "Web" When Blank
  20. 20. Trigger: Prevent Account Deletion with Related Opportunities
  21. 21. Trigger: Prevent Account Deletion with Related Contacts
  22. 22. Trigger: Set Contact_Created__c Checkbox on Account
  23. 23. Trigger: Auto-Create Default Contact on Account Insert
  24. 24. Trigger: Auto-Assign High Priority Cases to a Support Queue
  25. 25. Trigger: Prefix First Name with Dr for New/Updated Leads
  26. 26. Trigger: Update Account Rating to Hot on Closed Won Opportunity
  27. 27. Trigger: Append Phone Number to Account Name on Update
  28. 28. Trigger: Sync City Field from Account to Related Opportunities
  29. 29. Trigger: Cascade Custom Phone from Opportunity to Account and Contact
  30. 30. Trigger: Add Follow-Up Task When Opportunity Reaches Closed Won
  31. 31. Trigger: Divide Account Balance Equally Among Related Contacts
  32. 32. Trigger: Auto-Create Contact for High Annual Revenue Accounts
  33. 33. Trigger: Auto-Create Contact for Banking Industry Accounts
  34. 34. Trigger: Set Case Status and Priority for Email Origin Cases
  35. 35. Trigger: Set Lead Rating Based on Lead Source
  36. 36. Trigger: Set Contact OtherPhone from Parent Account Phone on Insert
  37. 37. Trigger: Assign Account Owner When Industry Is Education
  38. 38. Trigger: Cascade Contact AssistantPhone to Account and Opportunity
  39. 39. Set Account Rating to Hot on High Annual Revenue
  40. 40. Prevent Opportunity Closed Won Without an Amount
  41. 41. Prevent Duplicate Lead by Email
  42. 42. Auto-Generate Sequential Invoice Number
  43. 43. Prevent Employee From Being Their Own Manager
Easy TriggersGovernor

1. Bulk Account Rating Updater

Problem #1 · Salesforce Apex Coding Challenge

Problem Statement

Write a before insert / before update trigger on Account.

Set the Rating field based on AnnualRevenue:

  • Revenue > 1,000,000 → Rating = Hot
  • Revenue 100,000 – 1,000,000 → Rating = Warm
  • Revenue < 100,000 → Rating = Cold

Must handle bulk operations of up to 200 records without hitting governor limits.

Trigger Context Variables

  • Trigger.isBeforetrue when running in a before context
  • Trigger.isInserttrue on insert events
  • Trigger.isUpdatetrue on update events
  • Trigger.new — list of new/updated records (available on insert & update)

Constraints

  • No SOQL inside the loop
  • Must be fully bulkified
  • Handle null AnnualRevenue gracefully
Approach
  • 1Use Trigger.isBefore and (Trigger.isInsert || Trigger.isUpdate) to guard your logic — this is best practice even when the trigger event already implies the context.
  • 2Check acc.AnnualRevenue != null before comparing — null comparisons can throw NPE.
  • 3Use a simple if / else if / else chain inside the loop. No SOQL needed here.
  • 4This is a before trigger — mutate acc.Rating directly, no DML required.
Easy Triggers

2. Opportunity Close Date Validator

Problem #11 · Salesforce Apex Coding Challenge

Problem Statement

Write a before insert / before update trigger on Opportunity that prevents saving a record when the CloseDate is set to a date in the past.

Use addError() to surface a user-friendly validation message directly on the field.

Requirements

  • Trigger must fire on before insert and before update
  • Compare CloseDate against Date.today()
  • Call opp.CloseDate.addError() with a descriptive message
  • Handle bulk operations — no SOQL inside the loop
  • Allow CloseDate equal to today (same-day close is valid)

Example

CloseDate = yesterday → Error: "Close Date cannot be in the past."
CloseDate = today     → OK
CloseDate = tomorrow  → OK
Approach
  • 1Use opp.CloseDate < Date.today() — the < operator works directly on Date values in Apex.
  • 2Call opp.CloseDate.addError('Close Date cannot be in the past.') to attach the error to the field itself.
  • 3Before triggers block the DML automatically when addError() is called — no return or exception needed.
Easy Triggers

3. Employee Salary Validator

Problem #12 · Salesforce Apex Coding Challenge

Problem Statement

Write a before insert / before update trigger on Employee__c that validates the Salary__c field.

  • Salary must not be null
  • Salary must be greater than 0
  • Salary must not exceed $500,000

Use addError() on the field so the error appears inline in the UI.

Example

Salary__c = null    → Error: "Salary is required."
Salary__c = -1000  → Error: "Salary must be greater than 0."
Salary__c = 600000 → Error: "Salary cannot exceed $500,000."
Salary__c = 90000  → OK
Approach
  • 1Check emp.Salary__c == null || emp.Salary__c <= 0 in one condition.
  • 2Call emp.Salary__c.addError('Your message here') — the error attaches directly to the field.
  • 3Use else if for the upper bound so you only show one error per record.
Easy Triggers

4. Account Website Normalizer

Problem #13 · Salesforce Apex Coding Challenge

Problem Statement

Write a before insert / before update trigger on Account that normalizes the Website field.

If the Website is set but does not start with http:// or https://, prepend https:// automatically.

Examples

Website = null              → unchanged (skip)
Website = 'example.com'    → 'https://example.com'
Website = 'http://x.com'   → unchanged
Website = 'https://x.com'  → unchanged
Approach
  • 1Use acc.Website.startsWith('https://') — Apex String.startsWith() works just like Java.
  • 2Combine both checks: !startsWith http:// AND !startsWith https://
  • 3Prepend with: acc.Website = 'https://' + acc.Website;
Easy Triggers

5. Opportunity Amount Validator

Problem #14 · Salesforce Apex Coding Challenge

Problem Statement

Write a before insert / before update trigger on Opportunity that validates the Amount field must be greater than zero.

Null and zero amounts are both invalid — attach the error to the Amount field using addError().

Constraints

  • Bulk-safe — no SOQL inside the loop
  • Error must be on the Amount field, not on the record
Approach
  • 1opp.Amount == null evaluates to true in Apex when the field is blank.
  • 2Call opp.Amount.addError('Amount must be greater than zero.') to surface the error on the field.
  • 3One condition covers both cases: opp.Amount == null || opp.Amount <= 0
Easy Triggers

6. Case Priority Auto-Setter

Problem #16 · Salesforce Apex Coding Challenge

Problem Statement

Write a before insert / before update trigger on Case that automatically sets the Priority field based on keywords in the Subject.

  • Subject contains urgent, critical, or asap → Priority = High
  • Subject contains help or question → Priority = Low
  • All other non-null subjects → Priority = Medium
  • Null subject → skip (no change)

Matching is case-insensitive.

Approach
  • 1Use subjectLower.contains('urgent') — String.contains() checks for a substring.
  • 2Chain conditions: if / else if / else to avoid overwriting a valid priority.
  • 3Priority field accepts: 'High', 'Medium', 'Low'
Easy Triggers

7. Lead Score Auto-Assign Trigger

Problem #28 · Salesforce Apex Coding Challenge

Problem Statement

Write a before insert trigger on Lead that sets a custom Lead_Score__c field based on LeadSource:

  • Web → 80
  • Phone Inquiry → 60
  • Trade Show → 40
  • Everything else → 20
Approach
  • 1Use if/else if or a Map for cleaner code.
  • 2Default to 20 when no match.
Easy Triggers

8. Opportunity Amount Validation Trigger

Problem #30 · Salesforce Apex Coding Challenge

Problem Statement

Write a before insert, before update trigger on Opportunity that validates:

  • Amount must be greater than 0
  • CloseDate must not be in the past

If either condition fails, call addError() on the record with a descriptive message.

Approach
  • 1Date.today() gives today's date.
  • 2opp.addError('message') adds the error to the record.
Easy Triggers

9. Prevent Deletion of Won Opportunities

Problem #33 · Salesforce Apex Coding Challenge

Problem Statement

Write a before delete trigger on Opportunity that prevents deletion of any Opportunity with StageName = 'Closed Won'.

Add an error message: "Cannot delete a Closed Won opportunity."

Approach
  • 1Use Trigger.old to access records being deleted.
  • 2opp.addError('Cannot delete a Closed Won opportunity.');
Easy TriggersSOQL

10. Stamp Created-By Full Name on Record

Problem #40 · Salesforce Apex Coding Challenge

Problem Statement

Write a before insert trigger on Case that sets a custom text field Created_By_Name__c to the full name of the user inserting the record.

Use UserInfo.getUserId() to get the current user, then query the User object to get Name.

Approach
  • 1User u = [SELECT Name FROM User WHERE Id = :userId LIMIT 1];
  • 2Loop Trigger.new and set c.Created_By_Name__c = u.Name;
Easy Triggers

11. Lead Default Field Setter

Problem #79 · Salesforce Apex Coding Challenge

Problem Statement

Write a before insert trigger on Lead that sets default values for missing fields:

  • If LeadSource is null, set it to "Web"
  • If Status is null, set it to "New"
  • If Rating is null, set it to "Cold"

Key Concept — Before Insert Trigger

In a before insert trigger, Trigger.new contains the new records before they are saved. Records have no Id yet. You can modify fields directly — no DML needed. Trigger.old is null in before insert because there are no prior versions.

Constraints

  • Only use before insert — do not trigger on update
  • Do not overwrite values that are already set
  • Must be bulkified — handle 200 records in one transaction
Approach
  • 1Use: if (ld.LeadSource == null) ld.LeadSource = 'Web';
  • 2Before insert triggers mutate the record in memory — no DML required.
  • 3Trigger.old is always null in before insert — don't reference it here.
Easy TriggersAutomation

12. Auto-Create Contact for IT Industry Accounts

Problem #138 · Salesforce Apex Coding Challenge

Problem Statement

Write an after insert trigger on Account. Whenever a new account is created with Industry = 'Technology', automatically create a Contact for that account using:

  • Contact.LastName = Account.Name
  • Contact.Phone = Account.Phone
  • Contact.AccountId = the new account's Id

Best Practices

  • Use after insert (Ids are available after insert).
  • Build the full list of new Contacts first, then insert in a single DML call.
  • Skip accounts without Industry = 'Technology' silently.
Approach
  • 1Loop through Trigger.new; check acc.Industry == 'Technology'.
  • 2Build a List; add a new Contact(LastName=acc.Name, Phone=acc.Phone, AccountId=acc.Id).
  • 3Insert the list after the loop — one insert call regardless of batch size.
  • 4Test: insert 3 accounts — 2 IT + 1 other — assert exactly 2 contacts created.
Easy Triggers

13. Opportunity Probability Setter

Problem #144 · Salesforce Apex Coding Challenge

Problem Statement

Write a before insert / before update trigger called OpportunityProbabilityTrigger on Opportunity that automatically sets the Probability field based on StageName using the following mapping:

StageProbability
Prospecting10
Qualification20
Proposal/Price Quote60
Negotiation/Review80
Closed Won100
Closed Lost0

Requirements

  • Fire on before insert and before update
  • Use a Map<String, Integer> to store the stage-to-probability mapping
  • Only set Probability when StageName is non-null and exists in the map
  • Leave Probability unchanged for stages not in the map
  • Bulk-safe — handle up to 200 records with no SOQL inside the loop
Approach
  • 1Declare the map outside the loop so it is built once for the entire batch.
  • 2Use stageMap.containsKey(opp.StageName) before calling stageMap.get() to avoid null issues.
  • 3This is a before trigger — assign opp.Probability directly without DML.
Easy TriggersValidation

14. Trigger: Prevent Past Close Date on Opportunity

Problem #154 · Salesforce Apex Coding Challenge

Problem Statement

Write a before insert / before update trigger on Opportunity that prevents reps from setting a CloseDate in the past.

If CloseDate < Date.today(), call addError() on the CloseDate field with the message 'Close Date cannot be in the past.'

Best Practices

  • Use a before trigger — no DML needed.
  • Delegate logic to a handler class OpportunityTriggerHandler.
  • Bulkify: iterate Trigger.new, not a single record.
  • Guard null: only validate when CloseDate != null.
Approach
  • 1Use Date.today() to get today's date — no time-zone issues.
  • 2Call opp.CloseDate.addError('Close Date cannot be in the past.') inside the loop.
  • 3In a before trigger you do NOT need to call update — mutations apply automatically.
  • 4A good test covers: future date (passes), today (passes), yesterday (throws).
Easy Triggers

15. Trigger: Set Revenue Tier on Account

Problem #155 · Salesforce Apex Coding Challenge

Problem Statement

Write a before insert / before update trigger on Account that automatically sets a custom field Revenue_Tier__c based on AnnualRevenue:

  • > 10,000,000Enterprise
  • 1,000,000 – 10,000,000Mid-Market
  • 100,000 – 1,000,000SMB
  • < 100,000 or nullStartup

Best Practices

  • No SOQL — purely in-memory classification.
  • Handle null AnnualRevenue (classify as Startup).
  • Bulkify: handle 200 records in one invocation.
  • Delegate to handler class AccountTriggerHandler.
Approach
  • 1Check from highest to lowest to avoid overlap: > 10M first, then >= 1M, then >= 100K, else Startup.
  • 2AnnualRevenue is a Decimal — use >= and > for boundary checks.
  • 3A null AnnualRevenue falls to the else clause — no need for a separate null check if ordered correctly.
  • 4Test all four tiers plus a null revenue record.
Easy Triggers

16. Apex Trigger: Validate Contact Email Domain Before Save

Problem #217 · Salesforce Apex Coding Challenge

Problem Statement

Write a before insert / before update trigger on Contact that rejects any Email address that does not end with @salesforce.com.

Requirements

  • Fires on before insert and before update.
  • Skip Contacts where Email == null — null emails are allowed.
  • Call c.Email.addError('Email must be a @salesforce.com address.') to highlight the specific field.
  • Store the allowed domain in a constant (final String ALLOWED_DOMAIN).

Best Practices

  • Use .toLowerCase().endsWith() for case-insensitive comparison.
  • Call addError() on the field (c.Email.addError()), not on the record — this highlights the field in the UI.
  • Use a named constant for the domain string — never a magic string in the condition.
  • No SOQL or DML needed — pure in-memory validation.
Approach
  • 1Case-insensitive check: c.Email.toLowerCase().endsWith('@salesforce.com')
  • 2Field-level error: c.Email.addError('message') highlights the Email field specifically.
  • 3Skip nulls: if (c.Email == null) continue;
  • 4Constant: final String ALLOWED_DOMAIN = 'salesforce.com'; — avoids magic strings.
Easy Triggers

17. Trigger + Handler: Enforce Account Rating Based on Annual Revenue

Problem #226 · Salesforce Apex Coding Challenge

Problem Statement

Write a before insert / before update trigger on Account with handler AccountRatingHandler that sets Rating based on AnnualRevenue.

Rating Rules

  • Hot — AnnualRevenue ≥ 10,000,000
  • Warm — AnnualRevenue 1,000,000 – 9,999,999
  • Cold — AnnualRevenue < 1,000,000 or null

Best Practices

  • On updates, skip records where AnnualRevenue has not changed.
  • Handle null AnnualRevenue explicitly (assign Cold).
  • Tests cover all four paths: Hot, Warm, Cold (low), Cold (null), and unchanged update.
Approach
  • 1Handler signature: public static void setRating(List newList, Map oldMap)
  • 2Skip unchanged on update: if (oldMap != null && oldMap.get(acc.Id).AnnualRevenue == acc.AnnualRevenue) continue;
  • 3Null check first: if (acc.AnnualRevenue == null) { acc.Rating = 'Cold'; }
  • 4Test null revenue: new Account(Name='X') has no AnnualRevenue — should yield Cold.
Easy Triggers

18. Apex Trigger: Prevent Deletion of Closed Opportunities

Problem #230 · Salesforce Apex Coding Challenge

Problem Statement

Write a before delete trigger on Opportunity that prevents any closed Opportunity (IsClosed = true) from being deleted.

Requirements

  • Fires on before delete — use Trigger.old.
  • Loop Trigger.old; if opp.IsClosed == true, call opp.addError('Closed Opportunities cannot be deleted...').
  • No SOQL or DML needed — pure in-memory check on the record being deleted.

Best Practices

  • Use the formula field IsClosed — it is automatically true for both Closed Won and Closed Lost, so you do not need to check each stage name.
  • addError() on the record (not a field) prevents the delete and shows the message to the user.
  • No SOQL needed — all required data is available on Trigger.old.
Approach
  • 1IsClosed is a standard read-only formula field — true for both Closed Won and Closed Lost.
  • 2addError on record: opp.addError('message') — prevents the delete and surfaces error to UI.
  • 3No SOQL needed — all data is on Trigger.old already.
  • 4Test: insert a Closed Won opp → try delete → expect DmlException.
Easy Apex Trigger

19. Trigger + Handler: Default Lead Source to "Web" When Blank

Problem #287 · Salesforce Apex Coding Challenge

Problem Statement

Implement a Trigger + Handler that automatically sets LeadSource to 'Web' when a Lead is inserted without a source.

Trigger Requirements

  • Fire on before insert only
  • No logic in the trigger — delegate to the handler

Handler Requirements

  • Store the default value in a named constant
  • Only set LeadSource when it is blank/null — never overwrite an existing value
  • Bulk-safe: iterate over the full list

Test Class Requirements

  • Assert blank source defaults to 'Web'
  • Assert existing source is preserved
  • Bulk test with 200 leads
Approach
  • 1Fire the trigger on before insert only — there is no update scenario here.
  • 2Use String.isBlank(ld.LeadSource) to catch both null and empty string.
  • 3Store 'Web' in a constant so it is easy to change and self-documenting.
  • 4The bulk test asserts all 200 leads — query back with WHERE Id IN :leads.
Easy Triggers

20. Trigger: Prevent Account Deletion with Related Opportunities

Problem #324 · Salesforce Apex Coding Challenge

Problem Statement

Write an Apex trigger PreventAccountDeletion on the Account object that fires before delete.

If the Account being deleted has one or more related Opportunity records, call addError() on that Account to block the deletion with the message:
"Cannot delete an account with related opportunities."

Requirements

  • Bulk-safe: handle multiple Account deletions in one transaction.
  • Use a single SOQL query outside any loop.
  • Add the error on the Account record, not a field.

Expected Behaviour

  • Account with 0 Opportunities → deleted successfully.
  • Account with ≥ 1 Opportunity → blocked with error message.
Approach
  • 1Use Trigger.oldMap.keySet() to get the set of Account IDs being deleted.
  • 2Query SELECT Id FROM Opportunity WHERE AccountId IN :accountIds — one call outside any loop.
  • 3If the list is not empty, loop over Trigger.old and call acc.addError('Cannot delete an account with related opportunities.').
  • 4A before-delete trigger lets you call addError() directly on the sObject — no DML needed.
Easy Triggers

21. Trigger: Prevent Account Deletion with Related Contacts

Problem #325 · Salesforce Apex Coding Challenge

Problem Statement

Write an Apex trigger PreventAccountDeletionContacts on Account that fires before delete.

If any Account being deleted has one or more related Contact records, block deletion with addError() and the message:
"Cannot delete an account with related contacts."

Requirements

  • Bulk-safe — use aggregate SOQL, not a per-record query.
  • Use a Map<Id, Integer> to track counts per Account.
Approach
  • 1Use SELECT AccountId, COUNT(Id) cnt FROM Contact WHERE AccountId IN :accIds GROUP BY AccountId.
  • 2Cast aggregate results: (Id)ar.get('AccountId') and (Integer)ar.get('cnt').
  • 3Store in a Map<Id, Integer> then check countMap.containsKey(acc.Id) inside the loop.
  • 4Call acc.addError('Cannot delete an account with related contacts.') when count > 0.
Easy Triggers

22. Trigger: Set Contact_Created__c Checkbox on Account

Problem #330 · Salesforce Apex Coding Challenge

Problem Statement

Write an Apex trigger AccountForContact on Contact that fires after insert.

When a Contact is inserted with a non-null AccountId, set the checkbox field Contact_Created__c = true on the related Account.

Requirements

  • Collect all Account IDs from the batch in one pass.
  • Query only Accounts that need to be updated.
  • Issue a single bulk DML update.
Approach
  • 1Add the AccountId as a key to a Map<Id, Account> with a null value initially.
  • 2Query: SELECT Id, Contact_Created__c FROM Account WHERE Id IN :accountMap.keySet().
  • 3Set acc.Contact_Created__c = true and store the Account back in the map.
  • 4Call update accountMap.values() — the map now holds only Accounts that need updating.

Halfway there — solve them live

Every problem above runs in a real in-browser Apex editor with instant pass/fail feedback and best-practice linting. No Salesforce org needed.

Create Free Account →
Easy Triggers

23. Trigger: Auto-Create Default Contact on Account Insert

Problem #332 · Salesforce Apex Coding Challenge

Problem Statement

Write an Apex trigger DefaultContactOnAccount on Account that fires after insert.

For every newly created Account, automatically create a default Contact with the following values:

  • FirstName = 'Info'
  • LastName = 'Default'
  • Email = 'info@websitedomain.tld'
  • AccountId = the new Account's Id

Requirements

  • Create one Contact per Account in the batch.
  • Use a single bulk insert — no insert inside a loop.
Approach
  • 1Inside the loop, create new Contact(FirstName='Info', LastName='Default', Email='info@websitedomain.tld', AccountId=acc.Id).
  • 2Add each Contact to contactsToInsert.
  • 3Issue a single insert contactsToInsert outside the loop.
  • 4Guard with if (!contactsToInsert.isEmpty()) before the DML.
Easy Triggers

24. Trigger: Auto-Assign High Priority Cases to a Support Queue

Problem #335 · Salesforce Apex Coding Challenge

Problem Statement

Write an Apex trigger AutoAssignCaseOwner on Case that fires before insert.

If a Case is being created with Priority = 'High', automatically assign its OwnerId to the Id of the queue named 'High Priority Cases'.

Requirements

  • Query the Group object where Type = 'Queue' and Name = 'High Priority Cases'.
  • Guard against the queue not existing — only assign if the queue is found.
  • This is a before trigger — set OwnerId directly on the Case record.
Approach
  • 1Query: SELECT Id FROM Group WHERE Name = 'High Priority Cases' AND Type = 'Queue' LIMIT 1.
  • 2Store results in a List<Group> and check !queues.isEmpty() before using the Id.
  • 3Inside the loop: if (c.Priority == 'High') c.OwnerId = queueId;.
  • 4SOQL must be outside the loop — run it once before iterating Trigger.new.
Easy Triggers

25. Trigger: Prefix First Name with Dr for New/Updated Leads

Problem #338 · Salesforce Apex Coding Challenge

Problem Statement

Write an Apex trigger PrefixDrTrigger on the Lead object that fires before insert and before update.

For every Lead, if FirstName is not null and does not already start with 'Dr ', prepend 'Dr ' to FirstName.

Requirements

  • Fires on both insert and update.
  • Skip leads where FirstName is null or already starts with 'Dr '.
  • Bulk-safe: handles multiple leads in one transaction.
Approach
  • 1Use before insert, before update — modify Trigger.new directly, no DML needed.
  • 2Check l.FirstName != null before calling startsWith('Dr ').
  • 3Assign: l.FirstName = 'Dr ' + l.FirstName;
Easy Triggers

26. Trigger: Update Account Rating to Hot on Closed Won Opportunity

Problem #339 · Salesforce Apex Coding Challenge

Problem Statement

Write an Apex trigger UpdateAccountRating on the Opportunity that fires after insert and after update.

Whenever StageName is 'Closed Won', set the related Account's Rating to 'Hot'.

Requirements

  • Bulk-safe: one SOQL outside loop, one DML update.
  • Only update accounts linked to Closed Won opportunities.
Approach
  • 1Collect AccountIds where StageName == 'Closed Won'.
  • 2Query: SELECT Id, Rating FROM Account WHERE Id IN :accountIds.
  • 3Set acc.Rating = 'Hot' then update accounts.
Easy Triggers

27. Trigger: Append Phone Number to Account Name on Update

Problem #340 · Salesforce Apex Coding Challenge

Problem Statement

Write an Apex trigger UpdateAccountNameWithPhone on the Account that fires before update.

When the Phone field changes and is not null, set
Name = Name + ' - ' + Phone.

Requirements

  • Only fire when Phone actually changed.
  • Skip if the new Phone is null.
  • Bulk-safe.
Approach
  • 1Use Trigger.oldMap.get(acc.Id) to access previous Phone.
  • 2Set acc.Name = acc.Name + ' - ' + acc.Phone — no DML in before trigger.
Easy Triggers

28. Trigger: Sync City Field from Account to Related Opportunities

Problem #341 · Salesforce Apex Coding Challenge

Problem Statement

Write an Apex trigger SyncAccountCityToOpportunities on the Account that fires after update.

When City__c changes, update all related Opportunity City__c to match.

Requirements

  • Only sync when City__c actually changes.
  • Bulk-safe: one SOQL, one DML.
  • Use Trigger.newMap to get the new city value for each Opportunity.
Approach
  • 1Collect acc.Id when acc.City__c != Trigger.oldMap.get(acc.Id).City__c.
  • 2Query: SELECT Id, AccountId, City__c FROM Opportunity WHERE AccountId IN :accountIds.
  • 3Set opp.City__c = Trigger.newMap.get(opp.AccountId).City__c.
Easy Triggers

29. Trigger: Cascade Custom Phone from Opportunity to Account and Contact

Problem #342 · Salesforce Apex Coding Challenge

Problem Statement

Write an Apex trigger CascadePhoneFromOpportunity on the Opportunity that fires after update.

When TestPhoneOpportunity__c changes, sync the new value to the related Account's TestPhoneAccount__c and all related Contacts' TestPhoneContact__c.

Requirements

  • One SOQL with parent-to-child Contacts subquery.
  • Bulk-safe: one SOQL, separate DML for accounts and contacts.
Approach
  • 1Detect change: opp.TestPhoneOpportunity__c != Trigger.oldMap.get(opp.Id).TestPhoneOpportunity__c.
  • 2Query: SELECT Id, TestPhoneAccount__c, (SELECT Id, TestPhoneContact__c FROM Contacts) FROM Account WHERE Id IN :accountIds.
Easy Triggers

30. Trigger: Add Follow-Up Task When Opportunity Reaches Closed Won

Problem #343 · Salesforce Apex Coding Challenge

Problem Statement

Write an Apex trigger AddTaskOnClosedWon on the Opportunity that fires after insert and after update.

When StageName becomes 'Closed Won' (and was NOT already Closed Won on update), create a Task: Subject = 'Follow Up Test Task', WhatId = Opportunity Id, Status = 'Not Started', Priority = 'Normal'.

Requirements

  • On update: only create task when StageName transitions TO 'Closed Won'.
  • Bulk-safe: collect tasks, insert once.
Approach
  • 1Check opp.StageName == 'Closed Won'.
  • 2On update: skip if Trigger.oldMap.get(opp.Id).StageName == 'Closed Won'.
  • 3Create: new Task(Subject='Follow Up Test Task', WhatId=opp.Id, Status='Not Started', Priority='Normal').
Easy Triggers

31. Trigger: Divide Account Balance Equally Among Related Contacts

Problem #344 · Salesforce Apex Coding Challenge

Problem Statement

Write an Apex trigger DivideAccountBalance on the Account that fires after insert and after update.

For each Account with Balance__c > 0 and related Contacts, divide Balance__c equally by setting each Contact's IndividualBalance__c.

Requirements

  • Use parent-to-child SOQL with Contacts subquery.
  • Skip Accounts with no Contacts.
  • Bulk-safe: one SOQL, one DML.
Approach
  • 1Calculate: Decimal share = acc.Balance__c / acc.Contacts.size();
  • 2Inner loop contacts, set IndividualBalance__c = share, add to update list.
Easy Triggers

32. Trigger: Auto-Create Contact for High Annual Revenue Accounts

Problem #345 · Salesforce Apex Coding Challenge

Problem Statement

Write an Apex trigger AddContactForHighRevenue on the Account that fires after insert.

For every new Account where AnnualRevenue > 50000, create a Contact with FirstName = 'Smriti', LastName = 'Sharan', AccountId = Account Id.

Requirements

  • Only fires on insert.
  • Only create contact when AnnualRevenue is not null and > 50000.
  • Bulk-safe: collect contacts, insert once.
Approach
  • 1Null-check: acc.AnnualRevenue != null && acc.AnnualRevenue > 50000.
  • 2Create: new Contact(FirstName='Smriti', LastName='Sharan', AccountId=acc.Id).
Easy Triggers

33. Trigger: Auto-Create Contact for Banking Industry Accounts

Problem #346 · Salesforce Apex Coding Challenge

Problem Statement

Write an Apex trigger CreateContactForBanking on the Account that fires after insert.

For every new Account where Industry == 'Banking', create a Contact with LastName = Account Name, Phone = Account Phone, AccountId = Account Id.

Requirements

  • Fires only on insert, only when Industry = 'Banking'.
  • Bulk-safe: collect contacts, insert once.
Approach
  • 1Check acc.Industry == 'Banking'.
  • 2Create: new Contact(LastName=acc.Name, Phone=acc.Phone, AccountId=acc.Id).
Easy Triggers

34. Trigger: Set Case Status and Priority for Email Origin Cases

Problem #347 · Salesforce Apex Coding Challenge

Problem Statement

Write an Apex trigger SetCaseFieldsOnEmailOrigin on the Case that fires before insert.

For every new Case where Origin == 'Email', set Status = 'New' and Priority = 'Normal'.

Requirements

  • Fires only on insert, only when Origin = 'Email'.
  • No DML needed — it is a before trigger.
  • Bulk-safe.
Approach
  • 1Check c.Origin == 'Email'.
  • 2Set fields directly — no DML in a before trigger.
Easy Triggers

35. Trigger: Set Lead Rating Based on Lead Source

Problem #348 · Salesforce Apex Coding Challenge

Problem Statement

Write an Apex trigger SetLeadRatingBySource on the Lead that fires before insert.

For every new Lead: if LeadSource == 'Web'Rating = 'Hot'; otherwise → Rating = 'Cold'.

Requirements

  • Fires only on insert. No DML needed. Bulk-safe.
Approach
  • 1Use if/else: LeadSource == 'Web' → Rating = 'Hot', else → Rating = 'Cold'.
Easy Triggers

36. Trigger: Set Contact OtherPhone from Parent Account Phone on Insert

Problem #349 · Salesforce Apex Coding Challenge

Problem Statement

Write an Apex trigger SetContactOtherPhone on the Contact that fires before insert.

For every new Contact with an AccountId, look up the related Account's Phone and set the Contact's OtherPhone to that value.

Requirements

  • Skip contacts without AccountId.
  • One SOQL outside loop, Map for lookups.
  • No DML — before trigger.
Approach
  • 1Query: SELECT Id, Phone FROM Account WHERE Id IN :accountIds.
  • 2Build: new Map<Id, Account>([SELECT Id, Phone FROM Account WHERE Id IN :accountIds]).
  • 3Check containsKey before accessing the map.
Easy Triggers

37. Trigger: Assign Account Owner When Industry Is Education

Problem #350 · Salesforce Apex Coding Challenge

Problem Statement

Write an Apex trigger AssignOwnerByIndustry on the Account that fires before insert.

For every new Account where Industry == 'Education', assign ownership to the User with username 'smriti@sfdc.com'.

Requirements

  • Query User once outside loop. Guard if not found.
  • Only assign when Industry = 'Education'.
Approach
  • 1Query outside loop: SELECT Id FROM User WHERE Username = 'smriti@sfdc.com' LIMIT 1.
  • 2Guard: if (users.isEmpty()) return;
  • 3Check acc.Industry == 'Education' then set OwnerId.
Easy Triggers

38. Trigger: Cascade Contact AssistantPhone to Account and Opportunity

Problem #351 · Salesforce Apex Coding Challenge

Problem Statement

Write an Apex trigger CascadeAssistantPhone on the Contact that fires after update.

When AssistantPhone changes and is not null, sync it to the related Account's Phone and all related Opportunities' Phone__c.

Requirements

  • Only sync when AssistantPhone actually changes.
  • One SOQL with Account + Opportunities subquery. Bulk-safe.
Approach
  • 1Detect: c.AssistantPhone != null && c.AssistantPhone != old.AssistantPhone.
  • 2Query: SELECT Id, Phone, (SELECT Id, Phone__c FROM Opportunities) FROM Account WHERE Id IN :accountIds.
  • 3Update accounts then oppsToUpdate separately.
Easy TriggersValidation

39. Set Account Rating to Hot on High Annual Revenue

Problem #365 · Salesforce Apex Coding Challenge

Problem Statement

Write a before insert, before update trigger on the standard object Account that automatically sets a custom field Rating__c to "Hot" whenever AnnualRevenue exceeds 1,000,000.

Best Practices

  • Separate trigger logic into a handler class (AccountTriggerHandler).
  • Field-only logic belongs in before context — no DML/SOQL needed.
  • Bulkify: handle up to 200 records in one trigger invocation.

Objects / Fields

  • AccountAnnualRevenue (standard Currency), Rating__c (custom Picklist: Hot/Warm/Cold)
Approach
  • 1Loop through Trigger.new (works for both insert and update since it is before-context).
  • 2Check acc.AnnualRevenue != null && acc.AnnualRevenue > 1000000.
  • 3Set acc.Rating__c = 'Hot' directly on the in-memory record — no DML needed in before triggers.
  • 4Test: one Account above the threshold, one below, and a bulk list of 50 mixed records.
Easy TriggersValidation

40. Prevent Opportunity Closed Won Without an Amount

Problem #366 · Salesforce Apex Coding Challenge

Problem Statement

Write a before update trigger on the standard object Opportunity that blocks the StageName from being changed to "Closed Won" if Amount is null or 0.

Add a field error on Amount using addError().

Best Practices

  • Only trigger the check on an actual transition into Closed Won (compare against Trigger.oldMap).
  • Handler class pattern, bulk-safe for up to 200 records.

Objects / Fields

  • OpportunityStageName, Amount (both standard)
Approach
  • 1Compare opp.StageName to oldMap.get(opp.Id).StageName to detect a real transition into Closed Won.
  • 2Only block when the OLD stage was not already Closed Won (avoid re-blocking on unrelated edits).
  • 3Call opp.Amount.addError('...') so the error surfaces on the Amount field.
  • 4Test: transition with null Amount (blocked), with a positive Amount (allowed), and an unrelated field edit while already Closed Won (allowed).
Easy TriggersValidation

41. Prevent Duplicate Lead by Email

Problem #367 · Salesforce Apex Coding Challenge

Problem Statement

Write a before insert trigger on the standard object Lead that checks for duplicate leads based on Email and blocks insertion if a Lead with that email already exists.

Best Practices

  • Collect all incoming emails into a Set<String> — one SOQL query total.
  • Never query per record inside a loop.

Objects / Fields

  • LeadEmail (standard)
Approach
  • 1Build a Set of non-null emails from Trigger.new.
  • 2Query existing Leads: SELECT Email FROM Lead WHERE Email IN :emails — once, outside any loop.
  • 3Store the matches in a Set for O(1) lookups, then loop again to call addError() where matched.
  • 4Test: insert a Lead, then attempt a duplicate email and a unique email in the same handler call.
Easy TriggersAutomation

42. Auto-Generate Sequential Invoice Number

Problem #368 · Salesforce Apex Coding Challenge

Problem Statement

Write a before insert trigger on the custom object Invoice__c that auto-generates a unique Invoice_Number__c using the pattern INV-00001, INV-00002, etc.

Best Practices

  • Use one aggregate SOQL (MAX(...)) to find the last used sequence — no per-record query.
  • Pad the number using String.leftPad().
  • Production caveat: this pattern has a race condition under concurrent inserts. A native Auto Number field type is safer at scale — this exercise is for learning the aggregate-query + padding technique.

Objects / Fields

  • Invoice__cInvoice_Sequence__c (Number), Invoice_Number__c (Text)
Approach
  • 1Query MAX(Invoice_Sequence__c) from Invoice__c once, outside any loop.
  • 2Increment a running counter for each new Invoice__c in the batch (so a bulk insert of 5 gets 5 unique numbers).
  • 3Format with String.valueOf(seq).leftPad(5, '0') and prefix "INV-".
  • 4Test: first invoice → INV-00001; insert it; second invoice in a new call → INV-00002.
Easy TriggersValidation

43. Prevent Employee From Being Their Own Manager

Problem #369 · Salesforce Apex Coding Challenge

Problem Statement

Write a before update trigger on the custom object Employee__c that validates Manager__c is not self-referencing — an employee cannot be their own manager.

Best Practices

  • Only relevant on before update — on insert, the record has no Id yet, so self-reference is impossible at that point.
  • Pure in-memory check — no SOQL required.

Objects / Fields

  • Employee__cManager__c (self-lookup to Employee__c)
Approach
  • 1Loop through Trigger.new (the newList passed into the handler).
  • 2Compare emp.Manager__c to emp.Id — if equal and not null, that is a self-reference.
  • 3Call emp.addError('An employee cannot be their own manager.')
  • 4Test: self-reference (blocked), a different manager (allowed), and a bulk mix of both.

Practice All 43 Problems Free

Write and run real Apex code right in your browser — instant pass/fail feedback, best-practice linting, and governor limit monitoring. No Salesforce org needed.

Create Free Account → Explore All Problems
More Practice Topics
About ApexArena

ApexArena is a free, browser-based Salesforce Apex coding practice platform covering every major topic tested on the Salesforce Platform Developer I (PD1) and Platform Developer II (PD2) certification exams. All problems run directly in your browser with instant pass/fail feedback, best-practice linting (SOQL in loops, DML in loops, empty catch blocks), and governor limit monitoring — no Salesforce Developer Edition org required.

Related tutorials: Apex Triggers · SOQL · Batch Apex · Interview Q&A · Governor Limits