Prevent users from deleting an Account if it has any related Opportunities that are not in Closed Won or Closed Lost stages
Note: By default, Salesforce has built-in standard behavior that prevents users from deleting an Account if it has any associated Opportunities. However, if you need to restrict this deletion specifically based on the Opportunity stage (i.e., allowing deletion for Closed status but blocking it for Open status), a custom trigger like the one below is required.
trigger AccountTrigger on Account (before delete) {
if (Trigger.isBefore && Trigger.isDelete) {
Set<Id> accIds = new Set<Id>();
// Gather all Account IDs being deleted
for (Account acc : Trigger.old) {
accIds.add(acc.Id);
}
// Find Accounts that have at least one open Opportunity
Set<Id> accWithOpenOppr = new Set<Id>();
for (Opportunity oppr : [SELECT AccountId FROM Opportunity
WHERE AccountId IN :accIds
AND StageName NOT IN ('Closed Won', 'Closed Lost')]) {
accWithOpenOppr.add(oppr.AccountId);
}
// Loop through the records in Trigger.old to apply the error
for (Account acc : Trigger.old) {
if (accWithOpenOppr.contains(acc.Id)) {
// CRITICAL: .addError() must be called on the Account object itself
acc.addError('This account cannot be deleted because it has open Opportunities.');
}
}
}
}
Write a trigger to update Contact count on Account when Contacts are inserted or deleted.
Note: This is a simplified version ideal for coding interviews. While most interviewers will accept this approach initially to see your fundamental logic, you should be prepared for follow-up questions regarding bulkification and how it handles multiple record operations.
trigger ContactTrigger on Contact(after insert, after delete) {
Set<Id> accId = new Set<Id>();
if(Trigger.isInsert && Trigger.isAfter) {
for(Contact con: Trigger.new) {
if(con.AccountId != null) {
accId.add(con.AccountId);
}
}
}
if(Trigger.isDelete && Trigger.isAfter) {
for(Contact con: Trigger.old) {
if(con.AccountId != null) {
accId.add(con.AccountId);
}
}
}
if(!accId.isEmpty()) {
List<Account> accToUpdate = [SELECT Id, Name, Contact_Count__c FROM Account WHERE Id IN :accId];
for(Account acc: accToUpdate) {
Decimal currentContact = acc.Contact_Count__c == null ? 0 : acc.Contact_Count__c;
if(Trigger.isDelete) {
acc.Contact_Count__c = currentContact - 1;
}
if(Trigger.isInsert) {
acc.Contact_Count__c = currentContact + 1;
}
}
if(!accToUpdate.isEmpty()) {
update accToUpdate;
}
}
} Note: This is the ideal, production-grade solution that interviewers look for. It effectively showcases your knowledge of handling the complete trigger lifecycle (including undelete and update account-swapping logic) and proves you know how to bulkify Apex properly using Aggregate SOQL queries instead of dangerous math increments inside loops.
trigger ContactTrigger on Contact (after insert, after update, after delete, after undelete) {
Set<Id> accountIds = new Set<Id>();
// 1. Gather affected Account IDs based on the trigger context
if (Trigger.isInsert || Trigger.isUndelete) {
for (Contact con : Trigger.new) {
if (con.AccountId != null) {
accountIds.add(con.AccountId);
}
}
}
if (Trigger.isDelete) {
for (Contact con : Trigger.old) {
if (con.AccountId != null) {
accountIds.add(con.AccountId);
}
}
}
if (Trigger.isUpdate) {
for (Contact con : Trigger.new) {
Contact oldCon = Trigger.oldMap.get(con.Id);
// If the Contact was moved from one Account to another, track BOTH accounts
if (con.AccountId != oldCon.AccountId) {
if (con.AccountId != null) accountIds.add(con.AccountId);
if (oldCon.AccountId != null) accountIds.add(oldCon.AccountId);
}
}
}
// 2. Process and recalculate the counts
if (!accountIds.isEmpty()) {
// Pre-populate a map with 0 for all tracked accounts.
// This handles cases where the last remaining contact on an account is deleted.
Map<Id, Account> accountsToUpdateMap = new Map<Id, Account>();
for (Id accId : accountIds) {
accountsToUpdateMap.put(accId, new Account(Id = accId, Contact_Count__c = 0));
}
// 3. Aggregate Query: Group by AccountId to get the exact real-time count
List<AggregateResult> results = [SELECT AccountId, COUNT(Id) totalContacts
FROM Contact
WHERE AccountId IN :accountIds
GROUP BY AccountId];
// 4. Overwrite the map values with the actual database totals
for (AggregateResult ar : results) {
Id accId = (Id)ar.get('AccountId');
Integer count = (Integer)ar.get('totalContacts');
accountsToUpdateMap.get(accId).Contact_Count__c = count;
}
// 5. Update the Accounts in the database
update accountsToUpdateMap.values();
}
}
Prevent deleting a Contact if it is marked as Primary Contact on any Account.
trigger ContactTrigger on Contact (before delete) {
if (Trigger.isBefore && Trigger.isDelete) {
for (Contact con : Trigger.old) {
// Corrected the syntax error and typos here
if (con.AccountId != null && con.isPrimary__c) {
con.addError('Cannot delete this Contact because it is marked as the Primary Contact on Account ID: ' + con.AccountId);
}
}
}
}
Prevent deleting an Opportunity if it has any related Open Tasks.
trigger OpportunityTrigger on Opportunity (before delete) {
if (Trigger.isBefore && Trigger.isDelete) {
Set<Id> oppIds = new Set<Id>();
// 1. Gather all Opportunity IDs being deleted
for (Opportunity opp : Trigger.old) {
oppIds.add(opp.Id);
}
// 2. Query for Tasks where Status is exactly 'Open'
Set<Id> oppsWithOpenTasks = new Set<Id>();
for (Task tsk : [SELECT WhatId FROM Task WHERE WhatId IN :oppIds AND Status != 'Completed']) {
oppsWithOpenTasks.add(tsk.WhatId);
}
// 3. Prevent deletion by adding the error to the Opportunity record
for (Opportunity opp : Trigger.old) {
if (oppsWithOpenTasks.contains(opp.Id)) {
opp.addError('Cannot delete this Opportunity because it has open tasks associated with it.');
}
}
}
}
Restrict deletion of Account if it has more than 5 Contacts.
trigger AccountTrigger on Account (before delete) {
if (Trigger.isBefore && Trigger.isDelete) {
// 1. Get account IDs instantly without a loop
Set<Id> accIds = Trigger.oldMap.keySet();
// 2. Aggregate query
List<AggregateResult> groupedResults = [
SELECT AccountId, COUNT(Id) totalCount
FROM Contact
WHERE AccountId IN :accIds
GROUP BY AccountId
HAVING COUNT(Id) > 5
];
Set<Id> accWithMoreThan5Contacts = new Set<Id>();
// 3. Populate the set
for (AggregateResult ar : groupedResults) {
// The query already filtered for > 5, so we just grab the ID
accWithMoreThan5Contacts.add((Id)ar.get('AccountId'));
}
// 4. Add error to block deletion (Fixed .contacts() to .contains())
for (Account acc : Trigger.old) {
if (accWithMoreThan5Contacts.contains(acc.Id)) {
acc.addError('Cannot delete this Account because it has more than 5 related Contacts.');
}
}
}
}