name: formula-field-performance-and-limits description: "Use when diagnosing SOQL performance problems caused by formula fields, hitting formula compile-size limits, or deciding whether to replace a formula field with a stored field for indexing. Triggers: 'formula field slowing SOQL', 'compile size limit', 'cross-object formula spanning limit', 'formula field not indexable', 'LDV query full table scan', 'SOQL WHERE on formula'. NOT for formula syntax help, building formula expressions, or authoring new formulas from scratch — use admin/formula-fields for that." category: apex salesforce-version: "Spring '25+" well-architected-pillars:
- Performance
- Scalability
- Reliability tags:
- formula-fields
- performance
- soql
- ldv
- indexing
- governor-limits
- large-data-volumes inputs:
- "Object name and the formula field(s) suspected of causing the problem"
- "SOQL query text or debug log excerpt showing slow execution or full table scan"
- "Approximate record volume on the object (LDV threshold is typically 1M+ records)"
- "Whether the formula includes cross-object references (lookup traversals)" outputs:
- "Assessment of whether the formula field is causing a full table scan in SOQL"
- "Recommendation to convert formula to a stored field and keep it in sync via Flow/Process"
- "List of formula compile-size and cross-object spanning violations if present"
- "Revised SOQL or schema design that uses an indexed stored field instead" triggers:
- formula field causing full table scan on large object
- SOQL query slow because of formula field in WHERE clause
- formula compile size limit exceeded
- cross-object formula spanning relationship limit
- should I replace this formula field with a stored field for indexing dependencies: [] version: 1.0.0 author: Pranav Nagrecha updated: 2026-04-06
Formula Field Performance and Limits
This skill activates when formula fields are creating SOQL performance problems, hitting platform size limits, or when a practitioner must decide whether a calculated value should remain a formula or be materialized into a stored indexed field. It covers the compile-size limit (5,000 compiled characters), cross-object spanning limits (10 relationships), non-indexability of formula fields, and the on-read recalculation model that makes LDV SOQL filters on formulas especially expensive.
Before Starting
Gather this context before working on anything in this domain:
- Which object and which formula field(s) are involved? Get the full formula expression if possible — compile size is measured on the expanded compiled output, not the source text.
- What is the record volume on the object? Salesforce considers an object "Large Data Volume" (LDV) at roughly 1 million+ records. The formula-in-WHERE-clause problem becomes severe at this scale.
- Is the SOQL query filtering or sorting on the formula field? Check the SOQL WHERE clause and ORDER BY. A filter or sort on a formula field forces Salesforce to evaluate the formula for every row before filtering — there is no index to consult.
- How many cross-object hops does the formula traverse? Count every dot-notation lookup in the formula. The platform limits cross-object formula spanning to 10 unique relationships per object.
Core Concepts
Formula Fields Are Calculated On-Read, Not Stored
Formula fields do not write a value to the database row. Every time a record is retrieved, Salesforce evaluates the formula expression dynamically. This means:
- No trigger fires when the formula "changes" — because no change event occurs at the database level.
- You cannot subscribe to formula-field changes with Change Data Capture.
- There is no stored value to index. The platform cannot build a B-tree or hash index over a value that does not exist in the row until query time.
This is the root cause of every SOQL performance problem involving formula fields.
Formula Fields Are Not Indexable
Standard and custom indexes work by pre-computing a sorted structure over stored column values. Because formula fields produce their value at read time, Salesforce cannot include them in an index. The consequence for SOQL is concrete:
- A WHERE clause filtering on a formula field always produces a full table scan (or full custom index scan if only standard fields are also in the WHERE).
- On an object with 500,000+ records this can cause SOQL timeouts or MALFORMED_QUERY errors in reports and list views.
- Salesforce support cannot create a custom index on a formula field — the limitation is architectural, not a configuration choice.
The workaround is to mirror the formula result into a real stored field (Number, Text, Checkbox, etc.) using a Flow or Process Builder automation that fires on record save, then index that stored field.
Compile Size Limit: 5,000 Compiled Characters
Salesforce enforces a hard limit of 5,000 compiled characters for a formula field. "Compiled characters" are not the same as source characters typed in the formula editor:
- Field references are expanded to their full internal path during compilation (e.g.,
Account.Namebecomes the internal$ObjectType.Account.Fields.Nameequivalent). - Nesting
IF(),CASE(), andTEXT()calls with many branches can make a compact-looking formula exceed the limit. - The formula editor shows a character count for the source; the compiled count is only surfaced when the save fails with a limit error.
- Workaround: split complex logic across multiple simpler formula fields, or move computation to a Flow that writes to a stored field.
Cross-Object Formula Spanning: 10 Relationship Limit
A formula that traverses lookup relationships (e.g., Opportunity.Account.Owner.Profile.Name) counts each unique lookup relationship it spans. The platform limits a single formula to crossing at most 10 unique spanning relationships per object. Violations appear as validation errors at save time. Deep reference chains are also a performance concern: each additional hop increases the query cost when Salesforce evaluates the formula across millions of rows.
Common Patterns
Pattern 1: Materialize a Formula into a Stored Field for SOQL Filtering
When to use: A SOQL WHERE clause or ORDER BY needs to filter on a calculated value that is currently a formula field, and the object has significant record volume (typically 100,000+ records where index misses are measurable).
How it works:
- Create a new stored field (same data type as the formula output — Checkbox, Number, Text, Date, etc.) on the object. Name it clearly to indicate it mirrors the formula (e.g.,
Is_High_Value_Account__calongside an existing formulaIs_High_Value_Account_Formula__c). - Create a Record-Triggered Flow that fires on Create and Update (or a specific field-change condition to reduce cost).
- In the Flow update element, write the formula's calculated value into the stored field. Use a formula resource in the Flow to reproduce the same logic — do not reference the formula field directly if it introduces an extra read cost.
- Backfill existing records with a one-time Batch Apex job or Data Loader update.
- Request a custom index on the stored field via Salesforce Support, or confirm it qualifies for a selective query without one (selectivity: typically fewer than 10% of rows match the filter, or fewer than 30% with a compound index).
- Update any SOQL queries, reports, or list views to filter on the stored field instead of the formula field.
Why not the alternative: Leaving the formula field in the WHERE clause continues to force full table scan behavior. Queries that work today will degrade linearly as record count grows, typically surfacing as timeout errors in production before they appear in sandbox.
Pattern 2: Reduce Formula Compile Size by Splitting Across Helper Fields
When to use: A formula exceeds or approaches the 5,000 compiled-character limit, or a complex nested IF()/CASE() formula is becoming unmaintainable.
How it works:
- Identify logical sub-expressions within the formula that can stand alone (e.g., an intermediate lookup result or a boolean condition used multiple times).
- Create a helper formula field that computes only that sub-expression. Name it with a prefix like
Calc_to signal it is an intermediate computation, not a business-facing field. - Reference the helper field in the main formula. Salesforce expands field references at compile time, so referencing one simple formula field costs far fewer compiled characters than repeating the full sub-expression inline.
- Verify the new formula saves without a compile-size error. Use the formula editor character count as a guide but note it shows source characters, not compiled.
- Hide the helper field from page layouts and search if it has no independent business meaning.
Why not the alternative: Trying to minimize source text length by abbreviating variable names or collapsing whitespace does not reduce compiled size — the expansion happens on internal field paths, not on human-readable tokens.
Decision Guidance
| Situation | Recommended Approach | Reason |
|---|---|---|
| Formula field appears in SOQL WHERE clause on an LDV object | Materialize to stored field + Flow sync + custom index | Formula fields cannot be indexed; full table scan at scale causes timeouts |
| Formula compile size error on save | Split into helper formula fields | Reduces per-field compiled character count while preserving calculation logic |
| Cross-object formula approaching 10 spanning relationships | Flatten with a stored field on the child object written by Flow | Avoids the spanning limit and removes deep lookup overhead on read |
| Formula value must be tracked for Change Data Capture or trigger logic | Replace formula with stored field managed by automation | CDC and triggers require a real stored value to detect changes |
| Low-volume object (< 50,000 records) with formula in WHERE | Formula in WHERE clause is acceptable; monitor at growth | Full table scan cost is negligible at low volume; re-evaluate at scale |
| Formula used only for display on a page layout, no filtering | Keep as formula field | No indexing needed; on-read evaluation is fine for display purposes |
Recommended Workflow
Step-by-step instructions for an AI agent or practitioner working on this task:
- Confirm the SOQL query and object volume — Get the exact SOQL statement causing the concern and the approximate record count on the object. Run
SELECT COUNT() FROM ObjectNamein Developer Console or Query Plan Tool to confirm volume. Use the Query Plan Tool (Setup > Developer Console > Query Plan) to check whether Salesforce is performing a full table scan on the query. - Identify formula fields in the WHERE clause, ORDER BY, or GROUP BY — Review each filter criterion and sort field. Any formula field in these positions is a candidate for materialization. Confirm the field type with the Object Manager.
- Check for compile-size and spanning violations — Open the formula field in Setup > Object Manager > Fields & Relationships > Edit Formula. Attempt a save; the editor will report a compile-size error if applicable. Count the number of unique lookup traversals for cross-object formulas.
- Design the stored-field replacement — Choose the correct data type for the stored field. For boolean formulas use Checkbox; for numeric use Number or Currency; for text use Text. Draft the Flow logic to keep it in sync (Record-Triggered Flow on Create and Update).
- Backfill existing records — Write a Batch Apex class or use Data Loader to populate the stored field for all existing records before any SOQL query switches to filtering on it. A partial backfill will cause incorrect query results.
- Update SOQL and reports to use the stored field — Change all SOQL queries, list view filters, report filters, and dashboard components to reference the new stored field. The formula field can remain for display purposes.
- Validate with the Query Plan Tool — Re-run the Query Plan Tool against the updated SOQL. Confirm the plan shows an index operation (TableScan: false) rather than a full table scan before closing the work.
Review Checklist
- No formula field used as a filter, sort, or grouping criterion in SOQL on an object with significant record volume
- Formula fields approaching 5,000 compiled characters have been split into helper fields or replaced with stored fields
- Cross-object formulas have been reviewed for spanning relationship count (limit: 10)
- Any stored-field replacement has been backfilled for all existing records
- Record-Triggered Flow (or equivalent automation) is confirmed active and covers both Create and Update paths
- Query Plan Tool confirms index usage on updated SOQL queries
- CDC or trigger requirements that depend on detecting formula-value changes are addressed with stored fields
Salesforce-Specific Gotchas
-
Compile size is not source size — The formula editor displays source character count. The actual compiled size (which is what the 5,000-character limit applies to) expands internal field references and can be significantly larger. A formula that reads as 800 source characters can fail the compiled limit.
-
Formula fields never fire a trigger or CDC event — Because the value is not stored, Salesforce never writes a change to the row when the underlying data that the formula reads changes. Practitioners who rely on triggers or Change Data Capture to react to a "formula value change" will find that no event fires. The stored-field pattern with a Flow trigger is the only workaround.
-
Full table scan happens even with other indexed fields in the WHERE clause — A compound WHERE clause like
WHERE Status__c = 'Active' AND Formula_Field__c = truecan still produce a full table scan if the optimizer determines that the formula-field predicate is needed but non-selective. Having one indexed field in the clause does not protect against the formula predicate cost.
Output Artifacts
| Artifact | Description |
|---|---|
| Query Plan assessment | Result of running the Query Plan Tool against the target SOQL, confirming whether a table scan is occurring |
| Stored field design | Field name, data type, and API name for the materialized replacement field |
| Flow sync design | Record-Triggered Flow specification covering Create, Update, and any conditional entry criteria to minimize unnecessary evaluations |
| Backfill script | Batch Apex class or Data Loader instructions to populate the stored field on existing records |
| SOQL revision | Updated SOQL query using the stored field in place of the formula field |
Related Skills
admin/formula-fields— Formula syntax, operators, functions, and authoring guidance. Use when building or rewriting formula expressions rather than diagnosing performance.apex/governor-limits— Broader SOQL and DML governor limit patterns. Use when the formula performance issue is one part of a larger limit-consumption problem.apex/soql-fundamentals— SOQL query structure, selectivity rules, and index usage. Relevant when redesigning the full query around the stored field.data/sharing-recalculation-performance— LDV sharing and recalculation performance concerns that often co-occur with formula field performance issues on large objects.