Salesforce splits its objects into two categories with very different transactional behaviour:
- Setup objects — configuration: User, Profile, Group, GroupMember, QueueSObject, UserRole, PermissionSet, PermissionSetAssignment, ObjectPermissions, FieldPermissions, etc.
- Non-setup objects — business data: Account, Contact, Opportunity, Case, Lead, custom objects (
*__c), and every other day-to-day record.
You cannot do DML on both kinds in the same transaction — that throws the famous MIXED_DML_OPERATION error. Understanding the split is the only way to write code that touches users, profiles, or sharing rules without breaking.
The list — what counts as “setup”
The full list lives in the Salesforce help — Limitations of mixed DML, but the common ones an interview will probe:
- User — every CRUD operation on the user record
- UserRole
- PermissionSet, PermissionSetGroup, PermissionSetAssignment
- Profile
- Group, GroupMember
- QueueSObject
- ObjectPermissions, FieldPermissions, SetupEntityAccess
- CollaborationGroup and CollaborationGroupMember (Chatter groups)
- Territory, UserTerritory
If it controls who can do what, it’s almost certainly a setup object.
Why the rule exists
When you save business data (Account, Contact), the platform applies sharing rules. Sharing rule evaluation depends on the user/group/profile graph. If you could change a user’s profile and save an Account record in one transaction, you’d hit a chicken-and-egg problem: which version of the sharing rules do we use to evaluate the Account save — the old one, or the one with the new profile?
The platform sidesteps the whole question by forbidding mixed DML in a single user-facing transaction.
The error in action
// Throws System.DmlException: MIXED_DML_OPERATION
@isTest
static void mixedDmlInOneTx() {
User u = TestUtils.makeUser();
insert u;
Account a = new Account(Name = 'Acme');
insert a;
}
The four exceptions to the rule
There are four cases where mixed DML is permitted:
- Asynchronous Apex —
@future, Queueable, Batch, Scheduled. Each async transaction is fresh, so the platform doesn’t care about ordering against the parent. - The five recognised “user-related” objects —
Userfield updates can sometimes coexist with non-setup DML as long as the rest of the rules are followed. This is narrow and version-dependent; assume mixed DML is forbidden unless your test proves otherwise. - Test methods using
System.runAs()— wrapping the setup-object DML inSystem.runAs(adminUser) { ... }is one of the standard test-class workarounds. - Group membership and queue assignment in test context — special exceptions documented in the Help article.
The standard workaround: defer to @future
public class UserProvisioning {
public void onboard(Id contactId) {
Account a = new Account(Name = 'New Customer');
insert a; // non-setup
provisionUserAsync(contactId); // setup, in a different tx
}
@future
public static void provisionUserAsync(Id contactId) {
Contact c = [SELECT Id, Email FROM Contact WHERE Id = :contactId];
User u = new User(
Username = c.Email + '.uniq',
Email = c.Email,
// ...
);
insert u;
}
}
The insert u runs in a fresh transaction once the parent commits, so the rule isn’t violated.
In test methods: System.runAs
@isTest
static void mixedDmlInTest() {
User admin = [SELECT Id FROM User WHERE Profile.Name = 'System Administrator' LIMIT 1];
System.runAs(admin) {
User u = TestUtils.makeUser();
insert u; // setup DML
}
Account a = new Account(Name = 'Acme');
insert a; // non-setup DML — allowed because the user insert was inside runAs
}
System.runAs does more than change the running user — it also opens a sub-transaction boundary that lets test code do mixed DML legally.
Which objects don’t support DML at all?
A separate question — see “objects that do not support DML” — but worth distinguishing. Setup objects do support DML; they just can’t be mixed with non-setup DML in one transaction. AggregateResult, for instance, doesn’t support DML at all.
What interviewers are really looking for
The signal is knowing why the split exists — sharing rule evaluation order — not just memorising the error name. Bonus points for naming the four workarounds: @future, Queueable, Batch, and System.runAs in tests. Mention PermissionSetAssignment as a common setup object newcomers forget to put on the list.
Verified against: Salesforce Help — Limitations on Mixed DML, Apex Developer Guide —
@futureMethods. Last reviewed 2026-05-17.