Business Professionals
Power BI | Power Pivot | Power Query | DAX
Cloud Flows | RPA | AI Builder | Copilot
60+ Formulas | Data Stories | Advanced Reporting & Modeling
VB Programming | Report Automation |
MS-Office Automation
Techno-Business Professionals
Power BI | Power Query | Advanced DAX | SQL - Query &
Programming
Microsoft Fabric | Power BI | Power Query | Advanced DAX |
SQL - Query & Programming
Power BI | Power Apps | Power Automate | Copilot Studio | Power Pages | Dataverse
Microsoft Power Apps | Microsoft Power Automate
Power BI | Adv. DAX | SQL (Query & Programming) |
VBA | Python | Web Scrapping | API Integration
Power BI | Power Apps | Power Automate |
SQL (Query & Programming)
Power BI | Adv. DAX | Power Apps | Power Automate |
SQL (Query & Programming) | VBA | Python | Web Scrapping | API Integration
Power Apps | Power Automate | SQL | VBA | Python |
Web Scraping | RPA | API Integration
Technology Professionals
Power BI | DAX | SQL | ETL with SSIS | SSAS | VBA | Python
Power BI | SQL | Azure Data Lake | Synapse Analytics |
Data Factory | Databricks | Power Apps | Power Automate |
Azure Analysis Services
Microsoft Fabric | Power BI | SQL | Lakehouse |
Data Factory (Pipelines) | Dataflows Gen2 | KQL | Delta Tables | Power Apps | Power Automate
Power BI | Power Apps | Power Automate | SQL | VBA | Python | API Integration
Power BI | Advanced DAX | Databricks | SQL | Lakehouse Architecture
Business Professionals
Power BI | Power Pivot | Power Query | DAX
Cloud Flows | RPA | AI Builder | Copilot
60+ Formulas | Data Stories | Advanced Reporting & Modeling
VB Programming | Report Automation |
MS-Office Automation
Techno-Business Professionals
Power BI | Power Query | Advanced DAX | SQL - Query &
Programming
Microsoft Fabric | Power BI | Power Query | Advanced DAX |
SQL - Query & Programming
Power BI | Power Apps | Power Automate | Copilot Studio | Power Pages | Dataverse
Microsoft Power Apps | Microsoft Power Automate
Power BI | Adv. DAX | SQL (Query & Programming) |
VBA | Web Scrapping | API Integration
Power BI | Power Apps | Power Automate |
SQL (Query & Programming)
Power BI | Adv. DAX | Power Apps | Power Automate |
SQL (Query & Programming) | VBA | Web Scrapping | API Integration
Power Apps | Power Automate | SQL | VBA |
Web Scraping | RPA | API Integration
Technology Professionals
Power BI | DAX | SQL | ETL with SSIS | SSAS | VBA
Power BI | SQL | Azure Data Lake | Synapse Analytics |
Data Factory | Azure Analysis Services
Microsoft Fabric | Power BI | SQL | Lakehouse |
Data Factory (Pipelines) | Dataflows Gen2 | KQL | Delta Tables
Power BI | Power Apps | Power Automate | SQL | VBA | API Integration
Power BI | Advanced DAX | Databricks | SQL | Lakehouse Architecture
You inherit a forecasting workbook where every recalculation spins the fan, trace precedents looks like spaghetti, and one wrong row insert breaks half the model. The culprits are familiar: volatile OFFSET, opaque INDIRECT, and array-entered formulas buried in ranges. This article shows how to use Excel dynamic arrays in financial models to replace those patterns with safer, more transparent alternatives.
We'll work through one realistic scenario: a 3‑statement monthly model with driver sheets, scenario toggles and roll‑forward logic. We'll keep the structure, but swap out the fragile formulas for dynamic array patterns that survive row/column changes and scale cleanly.
You join a finance team maintaining a monthly forecast model for the next 36 months:
Drivers – assumptions (growth %, margins, capex, etc.) by monthScenarios – Base / Downside / Upside driver setsP&L, BalanceSheet, CashFlow – monthly statementsThe pain points:
Scenario switching via INDIRECT
In Drivers, each scenario is a named range (BaseDrivers, DownsideDrivers, etc.). A cell ScenSel holds the active scenario name. Formulas like:
=INDIRECT(ScenSel & "_Revenue")
pull the active scenario's revenue drivers.
Rolling ranges via OFFSET
In P&L, revenue forecast uses a rolling sum of the last 3 months:
=SUM(OFFSET(F7,0,MONTH(TODAY())-1,1,3))
This is volatile and fragile when columns are inserted.
Array‑entered formulas (CSE)
In several areas, matrix calculations are done with Ctrl+Shift+Enter, e.g. allocating overhead by driver proportions.
You want to:
Dynamic arrays (Microsoft 365 and Excel 2021+ with the modern calculation engine) give you the tools, but you need patterns that are safe in financial models.
Before refactoring, align on behaviour.
Dynamic arrays are available in:
Key behaviours:
FILTER, SORT, UNIQUE, SEQUENCE, XLOOKUP, XMATCH) are designed to return dynamic arrays.Important rules for financial models:
#SPILL! and does not partially overwrite.#, e.g. A1# refers to the entire spill range starting at A1.This makes spill ranges predictable, but you must manage layout to avoid accidental overlaps.
INDIRECT is volatile and breaks structured references, table resizing and refactoring. In our scenario, it's used to switch between scenario‑specific named ranges.
Example from Drivers:
=INDIRECT(ScenSel & "_Revenue")
Issues:
Instead of one named range per scenario, model scenarios in a single table.
Create a table tblDrivers on Drivers sheet with columns:
Scenario (text: "Base", "Downside", "Upside")LineItem (e.g. "Revenue", "COGS")Month (actual date or period code)Value (numeric driver)Put the active scenario in a cell ScenSel (data validation list: Base / Downside / Upside).
Use FILTER to spill the active scenario drivers:
=FILTER(tblDrivers[Value], tblDrivers[Scenario]=ScenSel)
This spills a vector of values for the selected scenario. To align with the monthly columns in P&L, filter by month as well.
Assume months in P&L are actual dates in row 5 (F5:AK5). In Drivers, add a Month column storing those same dates. Then:
=FILTER(tblDrivers[Value], (tblDrivers[Scenario]=ScenSel) * (tblDrivers[Month]=F$5))
This returns the driver for the active scenario and specific month. Copy across months; no INDIRECT, no volatility.
If you prefer a matrix layout:
Create a table tblScenarioDrivers:
| LineItem | Base | Downside | Upside |
|---|---|---|---|
| Revenue | 1.05 | 0.98 | 1.12 |
In Drivers, with ScenSel holding the scenario name, use:
=XLOOKUP(
"Revenue",
tblScenarioDrivers[LineItem],
INDEX(tblScenarioDrivers[[Base]:[Upside]], , XMATCH(ScenSel, tblScenarioDrivers[[#Headers],[Base]:[Upside]]))
)
Explanation:
XMATCH finds the column position of the active scenario within the scenario columns.INDEX(tblScenarioDrivers[[Base]:[Upside]], , column_number) returns the entire scenario column.XLOOKUP pulls the row for "Revenue" from that column.This is non‑volatile, fully traceable, and works cleanly with structured references.
OFFSET is another volatile function commonly used for rolling ranges (e.g. trailing 3 months). Dynamic arrays let you build those windows explicitly.
In P&L, a trailing 3‑month average revenue per month:
=AVERAGE(OFFSET(F7,0,MONTH(TODAY())-1,1,3))
Issues:
OFFSET and TODAY.Assume revenue row is 7, months are in columns F to AK, and you want trailing 3 months for each column.
In the first month where you have at least 3 months of history (e.g. column H for the 3rd month), use a dynamic array pattern:
Build relative column offsets with SEQUENCE.
For a trailing 3‑month window ending at the current column:
=SEQUENCE(3,1,-2,1)
This returns {-2;-1;0} as a vertical array.
Use INDEX to pick the corresponding cells from the revenue row.
In cell H8 (trailing 3‑month average for revenue row 7):
=LET(
RevenueRow, $F$7:$AK$7,
Offsets, SEQUENCE(3,1,-2,1),
CurrentPos, MATCH(H$5, $F$5:$AK$5, 0),
Window, INDEX(RevenueRow, Offsets + CurrentPos),
AVERAGE(Window)
)
Explanation:
RevenueRow is the full row of monthly revenue.MATCH(H$5, $F$5:$AK$5, 0) returns the position of the current month within the header row.Offsets + CurrentPos gives the positions of the 3‑month window.INDEX(RevenueRow, positions) returns a 3‑element array.AVERAGE(Window) computes the trailing average.This formula is non‑volatile (no OFFSET, no TODAY) and explicit about the window. It also adapts if you insert months, because MATCH and structured ranges update.
If you want the trailing 3‑month average for all P&L lines at once in a helper area, you can spill vertically and horizontally.
Assume P&L data is a table tblPL with:
LineItem columnIn a separate area, put:
=LET(
Data, tblPL[[F]:[AK]],
WindowSize, 3,
EndPos, COLUMNS(Data),
Offsets, SEQUENCE(WindowSize,1,-WindowSize+1,1),
WindowIndices, EndPos + Offsets,
RowIndex, SEQUENCE(ROWS(Data)),
Window, INDEX(Data, RowIndex, TRANSPOSE(WindowIndices)),
AVERAGE(Window)
)
This pattern:
SEQUENCE to construct column positions for the trailing window.INDEX to pull the window for all rows.Adjust EndPos if you want the window ending at a specific month rather than the last column.
Array‑entered formulas (Ctrl+Shift+Enter) are fragile because:
Dynamic arrays remove the need for CSE in most cases.
In P&L, overhead is allocated to business units based on driver proportions.
OverheadTotal in cell B10=B10 * (B20:D20 / SUM(B20:D20))
Array‑entered across B30:D30.
Move the formula to a single cell and let it spill.
In cell B30:
=LET(
TotalOverhead, B10,
Weights, B20:D20,
Normalized, Weights / SUM(Weights),
TotalOverhead * Normalized
)
This spills across B30:D30 automatically. If you add a fourth unit in column E:
Weights to B20:E20For more complex allocations, you can combine dynamic arrays with MMULT.
Assume:
You want a 3x3 allocation matrix: overhead categories × units.
In cell C30:
=LET(
Overhead, B10:B12,
Drivers, C20:E22,
Ones, SEQUENCE(ROWS(Drivers),1,1,0),
TotalDriversByUnit, MMULT(TRANSPOSE(Drivers), Ones),
NormalizedDrivers, Drivers / TotalDriversByUnit,
Allocation, MMULT(TRANSPOSE(Overhead), NormalizedDrivers),
Allocation
)
Notes:
SEQUENCE(ROWS(Drivers),1,1,0) creates a column vector of ones with the same row count as Drivers.MMULT(TRANSPOSE(Drivers), Ones) returns total driver per unit (1x3 vector).Drivers / TotalDriversByUnit normalises drivers per unit.MMULT(TRANSPOSE(Overhead), NormalizedDrivers) returns the 3x3 allocation matrix.This spills from C30, no CSE required. Be mindful of matrix dimensions; MMULT requires compatible sizes.
Dynamic arrays can create layout conflicts if you don't plan for spill ranges. In a column‑based financial model, you want dynamic arrays to be explicit and contained.
Rather than spilling directly into your primary statement area:
# or INDEX.Example: scenario‑specific revenue drivers.
On Drivers, in a helper area starting at G10:
=FILTER(tblDrivers, tblDrivers[Scenario]=ScenSel)
This spills the active scenario rows. In P&L, reference:
=XLOOKUP(
F$5,
INDEX(Drivers!G10#, , MATCH("Month", Drivers!G9#, 0)),
INDEX(Drivers!G10#, , MATCH("Value", Drivers!G9#, 0))
)
(Adjust header row reference and column labels to your actual layout or use structured references.)
This keeps spills away from your statement layout while still leveraging dynamic behaviour.
In team environments, someone will eventually type into a spill target.
Mitigation patterns:
Example:
FILTER formula ActiveScenarioDrivers.ActiveScenarioDrivers# in downstream formulas.If #SPILL! appears, it's easier to trace back to the named spill source.
Dynamic arrays don't automatically remove volatility. The functions themselves matter.
For financial models:
XLOOKUP, FILTER, INDEX, XMATCH, SEQUENCE, CHOOSECOLS, CHOSEROWS.OFFSET, INDIRECT, CELL, INFO, NOW, TODAY in core calc paths unless you genuinely need them.If you need rolling windows by period, use explicit period indices or dates rather than MONTH(TODAY()) so recalculation isn't tied to system date.
You rarely get to rebuild from scratch. A safe transition plan matters more than using every new function.
Use:
Formulas tab → Error Checking → Circular References (to ensure none first).Formulas → Show Formulas to visually scan for OFFSET, INDIRECT.Find & Select → Find for INDIRECT(, OFFSET(, { (array formulas show braces in the formula bar).List:
Start with:
INDIRECT)
FILTER/XLOOKUP.OFFSET)
INDEX + SEQUENCE.LET for clarity.Validate outputs against the old formulas before deleting them.
For each dynamic array block, add a short note next to it:
In shared models this prevents “helpful” edits that break spills.
Pick one live model and replace a single INDIRECT‑based scenario switch with a FILTER/XLOOKUP table pattern. Once you see that dynamic arrays can make the logic more explicit and less volatile without changing your sheet layout, the rest of the OFFSET and CSE clean‑up becomes a straightforward refactor instead of a risky rebuild.
This article reflects a shift from volatile, reference-based formulas to structured dynamic array patterns in Excel, particularly for finance teams maintaining multi-scenario, column-oriented models that need to stay stable under constant change.
Professionals who want to apply these patterns to their own data can explore Excelgoodies' Microsoft Excel Reporting programme - taught live by instructors, with certification awarded once a real project is running at work.
Insights compiled through ongoing industry research and discussions within the Excelgoodies Analytics Community.
Excel Formulas
New
Next Batches Now Live
Power BI
SQL
Power Apps
Power Automate
Microsoft Fabrics
Azure Data Engineering