Apex Trigger

Easy Apex Trigger Practice Problems

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

This guide walks through 53 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
  44. 44. Auto-Create Case When VIP Account Is Downgraded
  45. 45. Prevent Contact Creation Without Mobile Number for Premium Accounts
  46. 46. Create Renewal Task After Contract Activation
  47. 47. Prevent Case Reopening After Final Closure
  48. 48. Scenario: Flag High-Value Accounts for VIP Support
  49. 49. Scenario: Standardize Contact Phone Numbers on Save
  50. 50. Scenario: Default Case Origin for Web-Sourced Records
  51. 51. Update Account Industry Based on the First Opportunity Created
  52. 52. Automatically Create a Welcome Task for New Leads
  53. 53. Update Account Last Purchase Date After Opportunity Close
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 the related Opportunities for the whole batch of Account IDs in a single bulk call outside any loop — you only need enough fields to know which Accounts have at least one.
  • 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 an aggregate query on Contact that counts related records per Account, for the whole batch of IDs, in a single call.
  • 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 the Accounts that correspond to the keys already collected in accountMap, in a single bulk call, retrieving the checkbox field you need to set.
  • 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.
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 the Group object to find the specific queue named in the requirements, filtering to only Queue-type groups, and limit it to the one record you need.
  • 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 the Accounts for that batch of accountIds in one call, retrieving the Rating field you need to set.
  • 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.

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

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 the related Opportunities for the changed accountIds in one call, retrieving the AccountId and City__c fields you need to sync.
  • 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 the Accounts for the changed accountIds in one call, using a parent-to-child subquery to pull their related Contacts along with it so both can be updated without a second query.
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
  • 1Divide the Account's Balance__c by its number of related Contacts to get an equal share — use a Decimal so the split isn't truncated.
  • 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 the related Accounts for the batch of accountIds in one call, pulling back their Phone field.
  • 2Wrap that query directly in a new Map<Id, Account>(...) constructor so you get an Id-keyed lookup in one step.
  • 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 the User object once, outside the loop, filtering to the target username from the requirements, and limit it to one record.
  • 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 the Accounts for the batch of accountIds in one call, using a parent-to-child subquery to bring back each Account's related Opportunities (Id and Phone__c) alongside it.
  • 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 by email in one batched call — never per record inside a 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
  • 1A single aggregate query for the highest existing sequence, run once outside any loop, tells you where to resume numbering.
  • 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.
Easy Triggers

44. Auto-Create Case When VIP Account Is Downgraded

Problem #545 · Salesforce Apex Coding Challenge

Problem Statement

Write a trigger CreateCaseOnVipDowngrade on Account that fires after update.

Whenever an Account's Customer_Tier__c changes from 'VIP' to 'Standard', automatically insert a follow-up Case for that Account.

Requirements

  • Only create a Case when the tier actually transitions VIP → Standard.
  • Collect all Cases into a list and insert once, outside any loop.
Approach
  • 1Compare Trigger.oldMap.get(acc.Id).Customer_Tier__c to acc.Customer_Tier__c.
  • 2Build a List<Case> inside the loop, then call insert once after the loop.
Easy Triggers

45. Prevent Contact Creation Without Mobile Number for Premium Accounts

Problem #546 · Salesforce Apex Coding Challenge

Problem Statement

Write a trigger PreventContactWithoutMobileForPremium on Contact that fires before insert.

If a new Contact's parent Account has Customer_Tier__c = 'Premium' and the Contact's MobilePhone is blank, block the insert with addError().

Requirements

  • Use a single bulk SOQL query against Account, outside any loop.
  • Bulk-safe: handle multiple Contact inserts in one transaction.
Approach
  • 1Use String.isBlank(con.MobilePhone) to check for a missing value.
  • 2Query Account WHERE Id IN :accountIds AND Customer_Tier__c = 'Premium' once, then check membership with a Set.
Easy Triggers

46. Create Renewal Task After Contract Activation

Problem #548 · Salesforce Apex Coding Challenge

Problem Statement

Write a trigger CreateTaskOnContractActivation on Contract that fires after update.

When a Contract's Status transitions to 'Activated', automatically create a follow-up Task assigned to the Contract's OwnerId for renewal planning.

Requirements

  • Only fire when the status actually becomes Activated (not if it was already Activated).
  • Collect Tasks into a list and insert once, outside any loop.
Approach
  • 1Compare Trigger.oldMap.get(con.Id).Status to con.Status to detect the transition into Activated.
  • 2Set Task.WhatId to the Contract's Id so the Task shows up related to it.
Easy Triggers

47. Prevent Case Reopening After Final Closure

Problem #549 · Salesforce Apex Coding Challenge

Problem Statement

Write a trigger PreventCaseReopenAfterFinalClosure on Case that fires before update.

If a Case was Closed with its Final_Closure__c checkbox selected, block any attempt to change its Status away from 'Closed'.

Requirements

  • Only block when the Case was actually Closed AND flagged as a final closure.
  • Editing other fields on a finally-closed Case (without reopening it) must still work.
Approach
  • 1Read the case's prior state from Trigger.oldMap.get(c.Id).
  • 2Three conditions must ALL be true to block: old Status was Closed, old Final_Closure__c was true, and the new Status is no longer Closed.
Easy Triggers

48. Scenario: Flag High-Value Accounts for VIP Support

Problem #435 · Salesforce Apex Coding Challenge

Business Scenario

Support wants to proactively fast-track large customers. Whenever an Account is saved, set its checkbox VIP_Support__c to true if both AnnualRevenue >= 5,000,000 and NumberOfEmployees >= 250 — otherwise false.

Requirements

  • Runs on both insert and update, before save (no DML needed for the current record).
  • Both thresholds must hold — a huge company with no revenue on file, or a small high-revenue company, should not qualify.
  • Null AnnualRevenue/NumberOfEmployees must never qualify.
Approach
  • 1A before-insert/before-update trigger can set fields on Trigger.new directly — no DML statement is needed to persist them.
  • 2Guard both AnnualRevenue and NumberOfEmployees against null before comparing — a null Decimal compared with >= behaves unexpectedly.
  • 3Use named constants (VIP_REVENUE_THRESHOLD, VIP_EMPLOYEE_THRESHOLD) instead of bare numbers for readability and easy tuning.
Easy Triggers

49. Scenario: Standardize Contact Phone Numbers on Save

Problem #436 · Salesforce Apex Coding Challenge

Business Scenario

Sales reps enter Contact phone numbers in inconsistent formats (1234567890, 123-456-7890, (123) 456 7890...). Standardize every 10-digit US phone number to (XXX) XXX-XXXX on save.

Requirements

  • Strip all non-digit characters first, then reformat.
  • Only reformat when there are exactly 10 digits — leave anything else (international numbers, extensions, partial numbers) untouched.
  • Skip blank Phone values entirely.
Approach
  • 1String.isNotBlank(c.Phone) skips records with no phone number at all.
  • 2c.Phone.replaceAll('[^0-9]', '') strips every character that isn't a digit.
  • 3Only rebuild the formatted string when digitsOnly.length() == 10 — anything else should be left as-is.
  • 4String.substring(start, end) lets you slice out the area code, prefix, and line number.
Easy Triggers

50. Scenario: Default Case Origin for Web-Sourced Records

Problem #437 · Salesforce Apex Coding Challenge

Business Scenario

Cases created through the company's public web form arrive with SuppliedEmail populated (a self-service field) but sometimes no Origin set. Default Origin to "Web" whenever a new Case has a SuppliedEmail but no Origin yet.

Requirements

  • Only applies on creation — never override an Origin an agent later changes.
  • Never override an Origin the web form (or an integration) already set.
  • Only defaults when SuppliedEmail is present — internally-created Cases with no SuppliedEmail are unaffected.
Approach
  • 1A before insert trigger is enough here — this defaulting logic should never run on update.
  • 2String.isBlank(c.Origin) checks that no Origin was already supplied.
  • 3Only default when c.SuppliedEmail != null — that's the signal this Case came from the web form.
Easy Triggers

51. Update Account Industry Based on the First Opportunity Created

Problem #469 · Salesforce Apex Coding Challenge

Business Scenario

Data quality wants every Account's Industry field backfilled automatically the first time an Opportunity is created for it, inferring the industry from the Opportunity's LeadSource (e.g. a simple lookup table). Accounts that already have an Industry must be left alone.

Requirements

  • After insert trigger on Opportunity.
  • Only update Accounts whose Industry is currently blank.
  • If multiple new Opportunities in the same batch reference the same Account, only one update per Account should be attempted.

Best Practices

  • Query Accounts once with WHERE Industry = null so the trigger never touches an Account that already has a value — no redundant/overwriting updates.
  • Bulkify with Sets and Maps: one SOQL query and one DML statement regardless of how many Opportunities/Accounts are in the batch.
  • Wrap the update in try/catch(DmlException).
Approach
  • 1Query Accounts with Industry = null first — this both bulkifies the "already has a value" check and avoids a wasted update.
  • 2Use a Map keyed by AccountId so multiple Opportunities on the same new Account only produce one update.
  • 3A small private method (or inline if/else chain) can map LeadSource values like "Web" or "Referral" to an Industry string.
Easy Triggers

52. Automatically Create a Welcome Task for New Leads

Problem #470 · Salesforce Apex Coding Challenge

Business Scenario

Sales wants every new Lead to automatically get a follow-up Task assigned to its owner, due the next day, so no new Lead is ever missed.

Requirements

  • After insert trigger on Lead.
  • Create one Task per Lead: subject referencing the Lead's name, related to the Lead via WhoId, owned by the Lead's owner, due tomorrow.
  • Must handle a bulk insert of up to 200 Leads in one operation.

Best Practices

  • Build the full list of Tasks in memory first, then insert once — never insert inside the loop.
  • Wrap the DML in try/catch(DmlException) so one problematic Task (e.g. an owner without task-creation permission) doesn't throw an unhandled exception that blocks the whole trigger.
Approach
  • 1WhoId links a Task to a Lead or Contact (polymorphic) — use ld.Id here.
  • 2Date.today().addDays(1) gives tomorrow's date for ActivityDate.
  • 3Collect every Task into tasksToInsert inside the loop, then insert once after the loop ends.
Easy Triggers

53. Update Account Last Purchase Date After Opportunity Close

Problem #471 · Salesforce Apex Coding Challenge

Business Scenario

Account Management wants a custom field Last_Purchase_Date__c (Date) on Account automatically updated whenever one of its Opportunities transitions into Closed Won, reflecting the most recent purchase date.

Requirements

  • After update trigger on Opportunity.
  • Only act on a real transition into Closed Won (was not already Closed Won).
  • If several Opportunities for the same Account close in the same transaction, use the latest CloseDate among them.

Best Practices

  • Detect the transition using an index loop over Trigger.new/Trigger.old rather than Trigger.oldMap, since both lists are already index-aligned in an update context.
  • Aggregate the latest CloseDate per Account in a Map before any DML — one bulk update regardless of how many Opportunities closed.
  • Guard the update with a not-empty check and wrap it in try/catch(DmlException).
Approach
  • 1Compare newOpp.StageName == "Closed Won" && oldOpp.StageName != "Closed Won" to detect the transition.
  • 2Keep only the latest CloseDate per Account: compare against the current Map value before overwriting.
  • 3Build the Account update list from latestCloseDateByAccount.keySet(), one Account per key.

Practice All 53 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