Apex Triggers
1. What is Database.SOQL row lock (FOR UPDATE)?
Answer :
Adding FOR UPDATE to SOQL locks the selected records so no other transaction can update them until current transaction completes. Used to prevent race conditions. UNABLE_TO_LOCK_ROW error occurs when multiple concurrent transactions try to lock the same records simultaneously. Resolution: Implement retry logic, reduce batch concurrency, or restructure data processing to avoid shared record access.
2. What are the trigger events in Salesforce?
Answer :
before insert — record not yet saved; can modify field values
after insert — record saved; Id available; cannot modify triggering records
before update — record not yet saved with changes; can modify fields
after update — changes saved; oldMap and newMap available
before delete — record not yet deleted; Trigger.old available
after delete — record deleted; Trigger.old available
after undelete — records restored from Recycle Bin; Trigger.new available
Note: There is no 'before undelete'. Only after delete and after undelete exist for delete operations.
3. When do you choose before vs after trigger?
Answer :
Before trigger: When you need to validate or modify the triggering record's fields before it's saved — no DML needed on the same record (just assign field values). Ideal for field population, validation logic.
After trigger: When you need the record Id (post-insert), need to update related records, insert child records, or perform actions that shouldn't be part of the save operation. The triggering record is read-only in after context.
4. What is Trigger Framework / Handler Pattern?
Answer :
A trigger framework separates trigger logic from the trigger file itself. The trigger file only contains event routing to a handler class. Benefits: single responsibility, easier testing, recursion control, bypass logic.
// Trigger file (thin)
trigger AccountTrigger on Account(before insert, after insert, before update, after update) {
AccountTriggerHandler handler = new AccountTriggerHandler();
if(Trigger.isBefore && Trigger.isInsert) handler.onBeforeInsert(Trigger.new);
if(Trigger.isAfter && Trigger.isInsert) handler.onAfterInsert(Trigger.new, Trigger.newMap);
}
5. Write a Trigger: Roll-up count of Contacts on Account.
Answer :
trigger ContactTrigger on Contact(after insert, after update, after delete, after undelete) {
Set<Id> accountIds = new Set<Id>();
List<Contact> contacts = Trigger.isDelete ? Trigger.old : Trigger.new;
for(Contact c : contacts) {
if(c.AccountId != null) accountIds.add(c.AccountId);
}
if(Trigger.isUpdate) {
for(Contact c : Trigger.old) if(c.AccountId != null) accountIds.add(c.AccountId);
}
List<AggregateResult> results = [SELECT AccountId, COUNT(Id) cnt FROM Contact
WHERE AccountId IN :accountIds GROUP BY AccountId];
Map<Id, Integer> countMap = new Map<Id, Integer>();
for(AggregateResult ar : results) countMap.put((Id)ar.get('AccountId'), (Integer)ar.get('cnt'));
List<Account> toUpdate = new List<Account>();
for(Id accId : accountIds) {
toUpdate.add(new Account(Id = accId, Number_of_Contacts__c = countMap.containsKey(accId) ? countMap.get(accId) : 0));
}
update toUpdate;
}
6. Write a Trigger: Prevent deletion of Account if related Contacts exist.
Answer :
trigger AccountTrigger on Account(before delete) {
Map<Id, Account> accMap = new Map<Id, Account>([SELECT Id, (SELECT Id FROM Contacts LIMIT 1)
FROM Account WHERE Id IN :Trigger.old]);
for(Account acc : Trigger.old) {
if(!accMap.get(acc.Id).Contacts.isEmpty()) {
acc.addError('Cannot delete Account with related Contacts.');
}
}
}
7. Write a Trigger: When Account BillingCity is updated, update all related Contact MailingCity.
Answer :
trigger AccountTrigger on Account(after update) {
List<Account> changed = new List<Account>();
for(Account acc : Trigger.new) {
if(acc.BillingCity != Trigger.oldMap.get(acc.Id).BillingCity) changed.add(acc);
}
if(changed.isEmpty()) return;
Set<Id> accIds = new Map<Id, Account>(changed).keySet();
List<Contact> contacts = [SELECT Id, AccountId FROM Contact WHERE AccountId IN :accIds];
Map<Id, String> cityMap = new Map<Id, String>();
for(Account a : changed) cityMap.put(a.Id, a.BillingCity);
for(Contact c : contacts) c.MailingCity = cityMap.get(c.AccountId);
update contacts;
}
8. Write a Trigger: Only System Admin can delete Tasks.
Answer :
trigger TaskTrigger on Task(before delete) {
String profileName = [SELECT Name FROM Profile WHERE Id = :UserInfo.getProfileId()].Name;
if(profileName != 'System Administrator') {
for(Task t : Trigger.old) t.addError('Only System Administrators can delete tasks.');
}
}
9. Write a Trigger: Prevent duplicate Contact based on Email.
Answer :
trigger ContactTrigger on Contact(before insert, before update) {
Set<String> emails = new Set<String>();
for(Contact c : Trigger.new) if(c.Email != null) emails.add(c.Email);
Map<String, Contact> existingMap = new Map<String, Contact>();
for(Contact c : [SELECT Id, Email FROM Contact WHERE Email IN :emails]) {
existingMap.put(c.Email, c);
}
for(Contact c : Trigger.new) {
if(c.Email != null && existingMap.containsKey(c.Email) && existingMap.get(c.Email).Id != c.Id) {
c.addError('A contact with this email already exists.');
}
}
}
10. Write a Trigger: Sum of Opportunity Amounts on Account.
Answer :
trigger OpportunityTrigger on Opportunity(after insert, after update, after delete, after undelete) {
Set<Id> accIds = new Set<Id>();
for(Opportunity o : Trigger.isDelete ? Trigger.old : Trigger.new) if(o.AccountId != null) accIds.add(o.AccountId);
if(Trigger.isUpdate) for(Opportunity o : Trigger.old) if(o.AccountId != null) accIds.add(o.AccountId);
Map<Id, Decimal> sumMap = new Map<Id, Decimal>();
for(AggregateResult ar : [SELECT AccountId, SUM(Amount) total FROM Opportunity WHERE AccountId IN :accIds GROUP BY AccountId]) {
sumMap.put((Id)ar.get('AccountId'), (Decimal)ar.get('total'));
}
List<Account> toUpdate = new List<Account>();
for(Id id : accIds) toUpdate.add(new Account(Id = id, Total_Opportunity_Amount__c = sumMap.containsKey(id) ? sumMap.get(id) : 0));
update toUpdate;
}
11. What is the maximum trigger depth in Salesforce?
Answer :
Salesforce allows up to 16 levels of trigger recursion. After 16 recursive calls, a runtime exception is thrown. This is separate from the recursion within a single trigger execution.
💬 Community Chatter
Loading...🔐
Join the Community Discussion
Login to ask questions, share answers, and connect with other Salesforce professionals.