10 Practice Problems for APEX Triggers: A Beginner’s Guide

Apex triggers are essential tools in Salesforce development, allowing you to automate processes and create custom behaviors. Whether you’re a fresher or an experienced developer, practicing Apex trigger problems can significantly boost your understanding. In this post, I’ll walk you through 7 practice problems for Apex triggers that can help beginners get started and refine their skills. These problems are widely recognized and effective for learning.
Enhance your expertise with Salesforce online training in Admin, Developer, and AI modules. Gain hands-on experience through real-world projects to master Salesforce and accelerate your career.
Problem 1: Update Contact_Created__c
Checkbox on Account
Pre-Req:
Create a checkbox field on Account called Contact_Created__c
, default to off.
Assignment:
Create an Apex trigger on the Contact object that updates the Contact_Created__c
checkbox field to TRUE
when a Contact is created and associated with an Account.
Solution:
trigger AccountForContact on Contact (after insert, before update) {
if (Trigger.isInsert && Trigger.isAfter) {
Map<Id, Account> accountMap = new Map<Id, Account>();
// Collect Account IDs from newly inserted Contacts
for (Contact con : Trigger.New) {
if (con.AccountId != null) {
accountMap.put(con.AccountId, null);
}
}
// Query relevant Accounts and store in Map
if (!accountMap.isEmpty()) {
for (Account acc : [SELECT Id, Contact_Created__c FROM Account WHERE Id IN :accountMap.keySet()]) {
acc.Contact_Created__c = true;
accountMap.put(acc.Id, acc);
}
}
// Update Accounts in a single DML operation
if (!accountMap.isEmpty()) {
update accountMap.values();
}
}
}
Explanation:
This Apex trigger runs after a Contact is inserted and updates the related Account’s Contact_Created__c
field. It first collects unique AccountId
values from newly inserted Contacts and queries the corresponding Accounts using a Map<Id, Account>
. The trigger then updates the Contact_Created__c
field to true
for these Accounts and stores the changes in the map. Finally, it performs a single bulk update using update accountMap.values();
, ensuring efficiency and avoiding governor limits. This optimized approach enhances performance, scalability, and maintains best practices for handling large data volumes.
Problem 2: Mark Out_of_Zip
Checkbox on Account Based on Postal Code
Pre-Req:
Create a checkbox field on Account called Out_of_Zip
, default to off.
Assignment:
When an Account’s Billing Address is modified, check if any of the Contacts associated with that Account have a mailing postal code that is different from the Account’s Billing Postal Code. If so, mark the Out_of_Zip
checkbox to TRUE
.
Solution:
trigger TriggerExample on Account (before update, before insert) {
Set<Id> changedAccounts = new Set<Id>();
for (Account newAcc : Trigger.New) {
Account oldAcc = Trigger.OldMap != null ? Trigger.OldMap.get(newAcc.Id) : null;
if (oldAcc != null && (newAcc.BillingStreet != oldAcc.BillingStreet ||
newAcc.BillingCity != oldAcc.BillingCity ||
newAcc.BillingState != oldAcc.BillingState ||
newAcc.BillingPostalCode != oldAcc.BillingPostalCode ||
newAcc.BillingCountry != oldAcc.BillingCountry)) {
changedAccounts.add(newAcc.Id);
}
}
if (!changedAccounts.isEmpty()) {
Map<Id, Boolean> accountsOutOfZip = new Map<Id, Boolean>();
for (Contact c : [SELECT AccountId, MailingPostalCode, Account.BillingPostalCode
FROM Contact WHERE AccountId IN :changedAccounts]) {
if (c.MailingPostalCode != c.Account.BillingPostalCode) {
accountsOutOfZip.put(c.AccountId, true);
}
}
for (Account acc : Trigger.New) {
acc.Out_of_Zip__c = accountsOutOfZip.containsKey(acc.Id);
}
}
}
Explanation:
This Apex trigger runs before inserting or updating an Account and checks if the Billing Address fields have changed. It first compares Trigger.New
with Trigger.OldMap
(ensuring null safety) and adds modified Account IDs to a Set<Id>
. Next, it queries related Contacts whose MailingPostalCode
differs from their Account’s BillingPostalCode
and stores results in a Map<Id, Boolean>
. The trigger then efficiently updates the Out_of_Zip__c
field for Accounts in a single iteration based on whether any associated Contacts are outside the Account’s ZIP code. This approach ensures better bulk processing, governor limit optimization, and improved maintainability.
Problem 3: Create Default Contact When a New Account is Created
Pre-Req:
Create a checkbox field on Account called Only_Default_Contact
, default to off.
Assignment:
When a new Account is created, create a new Contact with specific details. If there are more than one Contact associated with the Account, update the Only_Default_Contact
checkbox to FALSE
.
Solution:
trigger defaultContact on Account (after insert) {
Set<Id> accountIds = new Set<Id>();
for (Account acc : Trigger.New) {
accountIds.add(acc.Id);
}
List<Account> accountsToUpdate = [SELECT Id, Only_Default_Contact FROM Account WHERE Id IN :accountIds];
List<Contact> contactsToInsert = new List<Contact>();
for (Account acc : accountsToUpdate) {
Contact con = new Contact();
con.FirstName = 'Info';
con.LastName = 'Default';
con.Email = 'info@websitedomain.tld';
con.AccountId = acc.Id;
contactsToInsert.add(con);
// Mark Only_Default_Contact to true
acc.Only_Default_Contact = true;
}
if (!contactsToInsert.isEmpty()) {
insert contactsToInsert;
}
if (!accountsToUpdate.isEmpty()) {
update accountsToUpdate;
}
}
Explanation:
This trigger runs after an Account is inserted and creates a default Contact for each newly inserted Account. It first collects the AccountId
values from Trigger.New
into a Set<Id>
to query the related Accounts. It then prepares a list of Contacts to insert, setting default fields like FirstName
, LastName
, and Email
. For each Account, the trigger also sets the Only_Default_Contact
field to true
. After preparing both the Contact and Account updates, it performs bulk-safe insert and update operations to handle multiple records efficiently.
Problem 4: Create an Account Record When a Contact is Created Without an Account
Assignment:
Create an Apex trigger that creates an Account record whenever a new Contact is created without an associated Account.
Solution:
trigger ContactCustomTriggerExample on Contact (after insert) {
List<Account> accListToInsert = new List<Account>();
for (Contact con : Trigger.New) {
// Only create a new Account if AccountId is null on Contact
if (con.AccountId == null) {
Account acc = new Account();
acc.Name = con.LastName;
acc.Phone = con.Phone;
accListToInsert.add(acc);
}
}
// Insert Accounts if there are any to insert
if (!accListToInsert.isEmpty()) {
insert accListToInsert;
}
}
Explanation:
This trigger fires after a Contact is inserted and checks if any of the Contacts do not have an associated AccountId
. For Contacts without an Account, a new Account is created, with fields like Name
(set to Contact’s LastName
) and Phone
. These new Accounts are added to a list, and if any Accounts are collected, they are inserted in bulk with a single DML operation. The approach ensures that only Contacts missing an Account trigger the creation of a new Account, optimizing performance.
Problem 5: Mark need_intel
Checkbox on Account Based on Dead Contacts
Pre-Req:
Create a checkbox field on Account called need_intel
, default to off, and a checkbox field on Contact called Dead
, default to off.
Assignment:
If 70% or more of the Contacts on an Account are marked as Dead
, mark the need_intel
field on the Account to TRUE
.
Solution:
public class ContactHandler {
public static void DeadIntel(List<Contact> cont) {
Set<Id> accIds = new Set<Id>();
for (Contact con : cont) {
if (con.AccountId != null) {
accIds.add(con.AccountId);
}
}
Map<Id, List<Contact>> accContactMap = new Map<Id, List<Contact>>();
List<Account> accUpdateList = new List<Account>();
for (Contact con : [SELECT AccountId, Dead__c FROM Contact WHERE AccountId IN :accIds]) {
List<Contact> contacts = accContactMap.get(con.AccountId);
if (contacts == null) {
contacts = new List<Contact>();
}
contacts.add(con);
accContactMap.put(con.AccountId, contacts);
}
for (Id accId : accContactMap.keySet()) {
Integer deadCount = 0;
Integer totalContacts = accContactMap.get(accId).size();
for (Contact con : accContactMap.get(accId)) {
if (con.Dead__c) {
deadCount++;
}
}
if ((deadCount * 100) / totalContacts >= 70) {
accUpdateList.add(new Account(Id = accId, needintel__c = true));
}
}
if (!accUpdateList.isEmpty()) {
update accUpdateList;
}
}
}
trigger UpdateContact on Contact (after update) {
if (Trigger.isUpdate && Trigger.isAfter) {
ContactHandler.DeadIntel(Trigger.New);
}
}
Explanation:
This code tracks dead Contacts in a set of Accounts and marks the associated Account’s needintel__c
field if more than 70% of its Contacts are marked as dead. The trigger runs after a Contact is updated and calls the DeadIntel
method, passing the updated Contacts to it. The method first collects Account IDs from the Contacts and queries the relevant Contacts to group them by AccountId
. It calculates the percentage of dead Contacts and updates the needintel__c
field on the Account if the threshold is met. The code efficiently handles bulk processing and minimizes DML operations by using maps and lists.
Problem 6: Auto-Assign Case Owner
Pre-requisites:
Understanding of before insert triggers.
Knowledge of Case Object and OwnerId field.
A predefined Queue in Salesforce (e.g., “High Priority Cases”).
Assignment:
Write a before insert trigger on the Case object.
If a new Case’s Priority is ‘High’, set its OwnerId to a specific Queue Id.
Solution:
We check the Case Priority before inserting and assign it to a predefined Queue.
Code:
trigger AutoAssignCaseOwner on Case (before insert) {
Id queueId = [SELECT Id FROM Group WHERE Name = 'High Priority Cases' AND Type = 'Queue' LIMIT 1].Id;
for (Case c : Trigger.new) {
if (c.Priority == 'High') {
c.OwnerId = queueId;
}
}
}
Explanation:
The code first retrieves the Queue Id for “High Priority Cases” using a SOQL query. It then iterates over Trigger.new and checks if the Priority of the Case is ‘High’. If it is, the Case’s OwnerId is updated with the Queue Id. This ensures that all high-priority cases are automatically assigned to the correct support team without manual intervention.
Problem 7: Update Profile__c
Field on Contact When Account Website is Updated
Pre-Req:
Create a text field on Contact called Profile
, 255 characters.
Assignment:
When an Account’s Website field is updated, update all related Contacts’ Profile__c
field to contain the Account’s website and the first letter of the Contact’s first name, followed by their last name.
Solution:
trigger TriggerExample3 on Account (after update) {
if (Trigger.isAfter && Trigger.isUpdate) {
Set<Id> accountIdSet = new Set<Id>();
// Collect Account IDs where Website is not null
for (Account ac : Trigger.New) {
if (ac.Website != null) {
accountIdSet.add(ac.Id);
}
}
// Query Contacts linked to the Accounts with a Website
List<Contact> contactList = [SELECT Id, FirstName, LastName, Profile__c, AccountId, Account.Website FROM Contact WHERE AccountId IN :accountIdSet];
// Update Profile__c with a URL-like value based on the Account Website and Contact Name
for (Contact con : contactList) {
if (con.FirstName != null) {
con.Profile__c = con.Account.Website + '/' + con.FirstName.substring(0, 1) + con.LastName;
}
}
// Update Contacts in bulk
if (!contactList.isEmpty()) {
update contactList;
}
}
}
Explanation:
This trigger runs after an Account update and checks for Accounts with a non-null Website
. For such Accounts, it queries their related Contacts and updates the Profile__c
field based on the Account.Website
and the Contact’s name. Specifically, it creates a URL-like string using the first letter of the FirstName
and the full LastName
of the Contact. The trigger performs a bulk update on Contacts to avoid hitting DML limits, ensuring optimal performance.
Problem 8: Count Contacts for Each Account
Pre-requisites:
Understanding of after insert & after delete triggers.
Knowledge of Aggregate SOQL for counting records.
A custom field ‘Number_of_Contacts__c’ on the Account object to store contact count.
Assignment:
Create an after insert & after delete trigger on Contact.
When a Contact is added or deleted, update the Account’s ‘Number_of_Contacts__c’ field.
Solution:
We recalculate the Contact count each time a Contact is inserted or deleted and update the related Account.
Code:
trigger UpdateContactCount on Contact (after insert, after delete) {
Set<Id> accountIds = new Set<Id>();
if (Trigger.isInsert || Trigger.isDelete) {
for (Contact c : Trigger.new != null ? Trigger.new : Trigger.old) {
if (c.AccountId != null) {
accountIds.add(c.AccountId);
}
}
}
if (!accountIds.isEmpty()) {
Map<Id, Integer> contactCounts = new Map<Id, Integer>();
for (AggregateResult ar : [SELECT AccountId, COUNT(Id) count FROM Contact WHERE AccountId IN :accountIds GROUP BY AccountId]) {
contactCounts.put((Id)ar.get('AccountId'), (Integer)ar.get('count'));
}
List<Account> accountsToUpdate = new List<Account>();
for (Id accId : accountIds) {
accountsToUpdate.add(new Account(Id = accId, Number_of_Contacts__c = contactCounts.get(accId) != null ? contactCounts.get(accId) : 0));
}
update accountsToUpdate;
}
}
Explanation:
This trigger first collects all Account Ids from the Contacts being inserted or deleted. It then runs an Aggregate SOQL query to count the number of Contacts per Account. A map stores these counts, which are used to update the Number_of_Contacts__c field on the Account. Finally, all affected Accounts are updated in one batch operation. This ensures that the Contact count for each Account remains accurate and up-to-date.
Problem 9: Prevent Account Deletion
Pre-requisites:
Basic understanding of Apex Triggers and Trigger Context Variables.
Knowledge of SOQL queries to check related records.
A Salesforce Developer Org to implement and test the trigger.
Assignment:
Create a before delete trigger on the Account object.
If an Account has related Contacts, prevent deletion and display an error message.
Solution:
We check for Contacts linked to the Account before deletion. If Contacts exist, we add an error message to prevent the deletion.
Code
trigger PreventAccountDeletion on Account (before delete) {
for (Account acc : Trigger.old) {
Integer contactCount = [SELECT COUNT() FROM Contact WHERE AccountId = :acc.Id];
if (contactCount > 0) {
acc.addError('You cannot delete this Account because it has related Contacts.');
}
}
}
Explanation:
This trigger runs before an Account is deleted. It loops through Trigger.old, which holds the records being deleted, and queries the number of Contacts linked to each Account. If an Account has related Contacts, addError()
prevents deletion. This ensures data integrity by preventing accidental deletion of Accounts with active Contacts.
Problem 10: Mark is_gold
Checkbox on Account Based on Opportunity Amount
Pre-Req:
Create a checkbox field on Account called is_gold
, default to off.
Assignment:
If an Opportunity related to an Account has an amount greater than $20,000, mark the is_gold
checkbox on the Account as TRUE
.
Solution:
trigger TriggerExample4 on Account (after update) {
Integer amount = 20000;
List<Account> accountsToUpdate = new List<Account>();
for (Account acc : Trigger.New) {
// Query Opportunities where Amount > 20000 for the related Account
List<Opportunity> opps = [SELECT Id FROM Opportunity WHERE AccountId = :acc.Id AND Amount > :amount];
// Set is_gold__c field based on the presence of qualifying Opportunities
acc.is_gold__c = (opps.size() > 0);
accountsToUpdate.add(acc);
}
// Update all modified Accounts in bulk to avoid multiple DML statements
if (!accountsToUpdate.isEmpty()) {
update accountsToUpdate;
}
}
Explanation:
This trigger executes after an Account is updated and checks whether the related Opportunities exceed a specified amount (in this case, 20,000). It queries the Opportunities associated with each Account and sets the is_gold__c
field to true if any Opportunity qualifies. The trigger collects all updated Accounts in a list to perform a single bulk update, reducing DML operations. The use of opps.size() > 0
ensures that only Accounts with qualifying Opportunities are marked as “gold.”
Conclusion
These 7 practice problems cover a variety of scenarios for Apex triggers in Salesforce, ranging from creating records automatically to updating fields based on certain conditions. Whether you’re just starting with Apex or looking to refine your skills, these problems will give you valuable experience in trigger development. If you have any questions, feel free to ask or dive into these examples using the Developer Console for hands-on practice!
Accelerate your career with Salesforce training in Hyderabad.
Unlock new opportunities with Salesforce training in Hyderabad, by advancing your skills in Admin, Developer, and AI modules. Our expert-led program combines theoretical knowledge with hands-on projects, ensuring a deep understanding of Salesforce concepts. Learn through real-world case studies and industry-driven assignments to sharpen your technical expertise and problem-solving abilities.
Gain an edge in the job market with personalized mentorship, interview coaching, and certification preparation. Equipped with practical exercises and comprehensive study materials, you’ll be ready to tackle complex business challenges using Salesforce.
Don’t wait—join our free demo session today and begin your journey to a successful Salesforce career in Hyderabad!