A recursive trigger is a trigger that ends up firing itself — directly or indirectly — because some logic inside it issues DML that hits the same object the trigger is bound to. Without a guard, the chain repeats until you blow a governor limit (Too many SOQL queries, CPU time limit exceeded, or Maximum trigger depth exceeded).
How it happens
Consider an after update trigger on Account that recalculates a field and writes the result back:
trigger AccountTrigger on Account (after update) {
List<Account> toUpdate = new List<Account>();
for (Account a : Trigger.new) {
Account copy = new Account(Id = a.Id);
copy.Health_Score__c = a.AnnualRevenue / 1000;
toUpdate.add(copy);
}
update toUpdate; // fires AccountTrigger again
}
The update toUpdate line triggers another after update invocation. That invocation runs the same logic, issues another update, fires the trigger again, and so on. Salesforce’s hard ceiling — 16 levels of trigger recursion — eventually throws, but you’ll typically hit the SOQL or DML governor limit first.
When recursion is legitimate vs accidental
Accidental recursion is the kind above — you write a record back and the same trigger re-runs your already-completed work.
Legitimate recursion happens when a trigger updates a different object whose trigger updates a third object that eventually updates the first. This is a real call graph, not a loop, but it can still revisit the original trigger context.
Recursion can also happen via workflow field updates and processes that re-fire triggers after the initial save (Trigger.isExecuting will still be true, but Trigger.isTriggerExecutionRecursive won’t help — that flag doesn’t exist).
The symptoms in production
System.LimitException: Too many SOQL queries: 101— the trigger ran 101 times, each issuing one query.System.LimitException: Apex CPU time limit exceeded— endless re-entry.Maximum trigger depth exceeded— Salesforce’s hard cap of 16.- Data inconsistency where
beforetriggers re-mutate fields the platform already wrote.
What Trigger.isExecuting does not tell you
Trigger.isExecuting is true whenever you’re inside any trigger context — not specifically whether this trigger has already run in this transaction. You need a static variable for that. The fix is covered separately in How to avoid recursive triggers.
Common interview follow-ups
- What’s the max recursion depth? — 16 levels, enforced by the platform as
Maximum trigger depth exceeded. - Do
beforeandaftertriggers both recurse? — Yes. Both fire again on any DML against the same object. - Does Flow recursion count toward the trigger depth? — Workflow field updates and processes that fire after the save can re-enter triggers, but they count against their own re-execution limit (usually 5 levels for workflow recursion).
Verified against: Apex Developer Guide — Triggers and Order of Execution, Execution Governors and Limits. Last reviewed 2026-05-17.