
The expensive part wasn't choosing Flow.
It wasn't choosing Apex either.
It was choosing one of them for the wrong reason.
Sometimes we picked Flow because it was faster to build.
Sometimes we picked Apex because the requirement sounded technical.
Both approaches worked initially.
Then the org grew.
More automation appeared. Data volumes increased. One team added another Flow. Another team introduced an Apex trigger. A related-record update triggered another automation path.
Eventually, debugging one field change meant tracing half the Salesforce org.
That changed how we thought about Flow vs Apex.
The question stopped being:
Can Flow do this?
or:
Can Apex do this?
The better question became:
Which approach will remain understandable, scalable, and maintainable when this process becomes more complicated?
1. We Chose Flow Because It Was Faster
The first requirement was straightforward:
When an Opportunity closes, update a few related records.
Flow handled it perfectly.
Opportunity Updated
↓
Check Conditions
↓
Get Related Records
↓
Calculate Values
↓
Update Records
No custom trigger.
No large Apex class.
The automation remained visible to administrators.
For that version of the requirement, Flow was the right choice.
Then the requirement grew.
Opportunity Updated
↓
Update Account
↓
Update Contract
↓
Update Forecast
↓
Check Partner Rules
↓
Recalculate Territory
↓
Create Follow-Up Records
↓
Trigger Integration
The Flow hadn't become a bad technology.
The problem had changed.
That's an important distinction.
A useful Salesforce automation strategy for choosing Flow vs Apex should consider not only how quickly an automation can be built, but also its complexity, dependencies, risk, ownership, and long-term maintenance cost.
Our mistake was treating the original design decision as permanent.
2. The Mega-Flow Became Hard to Understand
Visual automation is useful because the process is visible.
But visual does not automatically mean simple.
A Flow can eventually become:
Decision_1
Decision_2
Decision_2B
Get_Account
Get_Account_Again
Assignment_14
Update_Records_6
Subflow_ProcessSomething
Our largest Flows accumulated:
- Multiple decisions
- Record queries
- Cross-object updates
- Calculations
- Subflows
- Fault paths
- Asynchronous actions
- Integrations
The original argument had been:
Admins will be able to maintain it.
Eventually, even developers were hesitant to modify it.
That is a real cost.
Not only runtime cost.
Cognitive cost.
3. Some Logic Wanted Real Data Structures
This became one of the clearest signals that Apex might be a better fit.
Imagine needing to process hundreds of Opportunities grouped by Account.
In Apex:
Map<Id, List<Opportunity>> opportunitiesByAccount =
new Map<Id, List<Opportunity>>();
From there, Maps, Sets, helper methods, reusable classes, and explicit algorithms make complex transformations much easier to express.
Trying to model increasingly sophisticated transformations through dozens of visual elements can become harder to maintain than the equivalent code.
That doesn't mean:
Complex = always Apex.
It means the nature of the complexity matters.
When the requirement involves:
Large collections
Complex grouping
Advanced calculations
Reusable algorithms
High-volume transformations
Apex often becomes easier to reason about.
4. Then We Made the Opposite Mistake
After dealing with an oversized Flow, we became too eager to use Apex.
A requirement arrived:
Set a field when an Opportunity exceeds a threshold.
We could write:
trigger OpportunityTrigger on Opportunity (
before insert,
before update
) {
for (Opportunity opp : Trigger.new) {
if (opp.Amount > 100000) {
opp.Requires_Executive_Review__c = true;
}
}
}
Nothing is technically wrong with that.
But the requirement is also a natural candidate for a before-save Record-Triggered Flow.
Opportunity Before Save
↓
Amount > Threshold?
↓
Set Requires Executive Review = True
That version is easier for an administrator to understand and adjust later.
The lesson was:
Writing code doesn't automatically make the solution more engineered.
Sometimes it simply creates code someone must maintain.
5. Apex Increased Change Lead Time
The difference became obvious when business rules changed.
Suppose:
Amount > $100,000
becomes:
Amount > $150,000
With a well-designed Flow, that can remain a relatively small configuration change.
With Apex, the change normally stays within the development lifecycle:
Modify Apex
↓
Update tests
↓
Code review
↓
Deployment
That process is valuable when the complexity justifies it.
It is unnecessary overhead when the requirement is fundamentally simple.
Our rule became:
Use Apex because the problem benefits from Apex—not because a developer happens to be implementing the ticket.
6. Flow and Apex Started Competing on the Same Object
This was where troubleshooting became expensive.
Imagine:
Account Update
↓
Record-Triggered Flow
↓
Updates Contact
↓
Contact Automation
↓
Updates Account
↓
Account Apex Trigger
↓
Updates Custom Object
↓
Another Flow
Now ask:
Why did this field change?
The answer may be spread across several automation mechanisms.
That leads to:
- Difficult execution-order reasoning
- Recursion risk
- Duplicate logic
- Harder debugging
- Multiple ownership boundaries
The issue wasn't:
Flow + Apex = bad
The issue was:
Uncoordinated Flow
+
Uncoordinated Apex
+
More automation
=
No clear owner
Once an object becomes heavily automated, architecture needs a clear center of gravity.
7. Hybrid Architecture Worked Better
Eventually, Flow vs Apex stopped being a competition.
Some processes genuinely benefited from both.
Imagine:
Case Created
↓
Determine SLA Type
↓
Calculate SLA Deadline
↓
Assign Queue
↓
Notify Team
The orchestration is easy to understand visually.
Flow is a natural fit.
But SLA calculations may involve:
Business hours
Holidays
Service levels
Time zones
Complex date calculations
That logic may be easier to isolate in Apex.
A better design is:
Record-Triggered Flow
↓
Determine Process Path
↓
Invocable Apex
↓
Complex Calculation
↓
Return Result
↓
Continue Flow
Flow owns the business process.
Apex owns the specialized computation.
That hybrid model often gave us the best balance between maintainability and control.
8. Hybrid Doesn't Mean Calling Apex Everywhere
Hybrid architecture can go too far.
Flow
↓
Apex
↓
Subflow
↓
Apex
↓
Another Flow
↓
Apex
Now the architecture is fragmented again.
We started asking:
If someone needs to understand this business process, where should they look first?
If the answer is Flow, Flow should actually own the orchestration.
If the answer is an Apex trigger framework, the transaction shouldn't secretly depend on several unrelated Flows.
There needs to be a clear architectural owner.
9. High-Volume Data Exposed Weak Designs
An automation can work perfectly when one salesperson edits one record.
Then a Data Loader import arrives.
Or an integration updates several thousand records.
Now the workload looks very different.
Bulk API
↓
Thousands of records
↓
Flows
↓
Queries
↓
Loops
↓
Related updates
↓
Other automation
Suddenly:
- SOQL usage matters
- DML usage matters
- CPU time matters
- Recursion matters
- Downstream automation matters
This is where understanding Salesforce Flow loops best practices becomes especially useful. Collections, bulk updates, avoiding DML inside loops, and recognizing when large workloads should move toward Apex or batch processing can prevent an otherwise clean Flow from becoming a production bottleneck.
The lesson wasn't:
Flow cannot process collections.
It can.
The lesson was:
Don't validate production architecture using only one-record tests.
10. Avoid Database Work Inside Loops
One of the easiest Flow performance mistakes looks conceptually like:
Loop Records
↓
Update Record
↓
Next Record
A healthier approach is:
Loop Records
↓
Modify Collection
↓
Finish Loop
↓
Update Collection Once
The same principle exists in Apex.
Bad:
for (Contact contact : contacts) {
update contact;
}
Better:
for (Contact contact : contacts) {
// modify values
}
update contacts;
This is a useful reminder that good automation architecture matters more than whether the implementation is declarative or programmatic.
You can build inefficient Flow.
You can also build inefficient Apex.
11. Transaction Control Was a Strong Apex Signal
Some requirements eventually needed:
Savepoints
Rollback behavior
Partial success
Fine-grained exception handling
Complex transactional logic
Those requirements made the decision easier.
Apex gives developers much more explicit transaction control.
When we genuinely needed that level of control, forcing the workflow to remain entirely declarative created more complexity than it removed.
That became another rule:
Let the requirement reveal when you've crossed the boundary into Apex.
12. Error Handling Influenced the Decision
Flow can provide useful fault paths:
Action
↓
Failure
↓
Fault Connector
↓
Log
↓
Notify
For many business processes, that is enough.
Other scenarios require:
Record A succeeds
Record B fails
Record C succeeds
Record D needs retry
Custom user error required
When failure handling becomes this detailed, Apex often provides cleaner control.
Again, the goal isn't picking a winner.
The goal is matching the tool to the failure model.
13. Flow Still Needs Testing Discipline
One dangerous attitude is:
It's Flow, so we'll just click Debug.
That isn't enough for production automation.
The workflow should be tested against scenarios like:
Expected record
Unexpected record
Missing related data
Bulk processing
Negative case
Permission differences
Fault path
Think:
Define behavior
↓
Build Flow
↓
Create repeatable tests
↓
Exercise important paths
↓
Deploy
↓
Regression test changes
Declarative automation is still application logic.
It deserves the same engineering discipline.
14. Apex Coverage Doesn't Guarantee Good Architecture
The opposite assumption also causes problems.
Code coverage = 90%
doesn't mean the automation is well designed.
A test may execute the code without proving the actual business outcome.
Instead of asking:
Did these lines run?
ask:
Given:
An enterprise Opportunity above the threshold
When:
The Opportunity closes
Then:
Executive review is required
and the expected downstream behavior occurs
Test business behavior.
Not only implementation mechanics.
That rule applies equally to Apex and Flow.
15. Entry Conditions Were One of the Cheapest Optimizations
A Flow that never needs to run is cheaper than one that runs and exits later.
Instead of:
Every Account Update
↓
Start Flow
↓
Check whether relevant fields changed
prefer precise start criteria where possible:
Relevant change occurred?
↓
YES
↓
Run automation
This reduced:
- Unnecessary executions
- Extra queries
- Unexpected downstream actions
- Debugging noise
One of our simplest architecture rules became:
Make the automation prove that it needs to run before doing any work.
16. Before-Save vs After-Save Matters Too
Sometimes the real decision isn't simply:
Flow or Apex?
It is:
Before-save Flow?
After-save Flow?
Flow + Invocable Apex?
Apex Trigger?
Async Apex?
Scheduled Flow?
Batch Apex?
For simple same-record updates:
Record changes
↓
Calculate
↓
Set fields
a before-save Flow can be an excellent fit.
For:
Create related record
Update another object
Send notification
Trigger integration
after-save processing is usually more appropriate.
Choosing the correct execution model can matter as much as choosing the language.
What Choosing Wrong Actually Cost
The cost wasn't just additional developer hours.
Maintenance Cost
Simple requirement
+
Unnecessary Apex
=
Permanent developer dependency
Debugging Cost
Flow
+
Trigger
+
More automation
+
No clear ownership
=
Long incident investigation
Performance Cost
Complex Flow
+
Large record volume
+
Poor bulk design
=
Limit pressure
Change Cost
Mega-Flow
+
Years of patches
=
Nobody wants to touch it
Governance Cost
Multiple teams
+
No automation standard
=
Automation sprawl
That final one was often the most expensive.
The Decision Framework We Use Now
Choose Flow When
Business logic is straightforward
Automation density is relatively low
Admins benefit from visibility
Same-record updates are simple
Cross-object logic remains manageable
Visual orchestration improves understanding
Declarative ownership has real value
Choose Apex When
Automation density is high
Large data volumes are common
Complex data transformations are required
Maps, Sets, and algorithms improve clarity
Precise transaction control is required
Performance needs tight control
Error handling is sophisticated
A dedicated trigger framework is justified
Choose Flow + Invocable Apex When
The business process is visually understandable
But one part contains complex computation
Flow should own orchestration
Apex can expose a small reusable capability
The organization needs both admin visibility
and developer-level control
The point is not to minimize Apex.
It is not to maximize Flow.
It is to keep the complexity of the solution proportional to the complexity of the requirement.
Practical Flow vs Apex Checklist
Before building automation, ask:
[ ] How much automation already exists on this object?
[ ] Is this simple same-record logic?
[ ] How many related records will be touched?
[ ] What data volume must this handle?
[ ] Does the logic require complex collections?
[ ] Do Maps or Sets make the implementation clearer?
[ ] Do we need precise transaction control?
[ ] What happens when one record fails?
[ ] Who will maintain the automation?
[ ] Should administrators be able to change it?
[ ] Does the complexity actually justify Apex?
[ ] Would Flow + Invocable Apex create a cleaner boundary?
[ ] Are execution-order assumptions clear?
[ ] Are Flow and Apex competing on the same object?
[ ] Are entry criteria narrow?
[ ] Has bulk behavior been tested?
[ ] Can the important behavior be regression-tested?
[ ] Is the implementation version-controlled?
The Biggest Lesson
Our biggest mistake in the Flow vs Apex debate was assuming one of them needed to win.
It doesn't.
Flow is not "Apex for admins."
Apex is not "Flow for developers."
They are different tools with overlapping capabilities.
Our thinking changed from:
Can Flow do this?
to:
Should Flow own this?
And from:
Can we implement this in Apex?
to:
Does the requirement actually justify Apex?
The answer can also change over time.
A Flow that is ideal today may become part of a hybrid design as automation density grows.
A complex transaction may eventually justify an Apex trigger framework.
A tiny Apex trigger written years ago may now be easier to maintain as a before-save Flow.
Changing architecture as the system changes isn't failure.
Ignoring the changing system is.
The best choice isn't Flow.
It isn't Apex.
It is the approach whose complexity, scale, ownership, testing model, and maintenance cost match the actual problem.
Top comments (0)