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
If you're still leaning on OFFSET, legacy array formulas, and volatile tricks, you're leaving performance and maintainability on the table. This article shows how to replace those patterns with LET, LAMBDA, and Dynamic Arrays, using concrete patterns you can drop into production models and report templates. If you want to turn these ideas into end‑to‑end reporting workflows, pair them with solid Excel reporting foundations.
Before jumping into modern patterns, it helps to name the usual suspects you probably have in your models today:
=SUM(OFFSET(B2,0,0,12,1))=OFFSET(Sheet1!$A$1,0,0,COUNTA(Sheet1!$A:$A),1)INDEX/MATCH arraysINDIRECT to build references from textNOW, TODAY, RAND, RANDBETWEEN used as cheap recalc triggersThese patterns work, but they:
The goal: move to non-volatile, readable, reusable formulas built on:
LET for named sub-expressionsLAMBDA for reusable custom functionsA classic dynamic named range for a growing list:
=OFFSET(Sheet1!$A$2,0,0,COUNTA(Sheet1!$A:$A)-1,1)
Issues:
OFFSET is volatileUse INDEX (non-volatile) with full-column references and SEQUENCE where needed.
Example: Last 12 months of sales from a growing column
Old:
=SUM(OFFSET(B2,COUNTA(B:B)-12,0,12,1))
New:
=LET(
data, B:B,
n, COUNTA(data),
last12, INDEX(data, SEQUENCE(12, 1, n-11)),
SUM(last12)
)
Why this is better:
OFFSETdata, n, last1212 in one place)If you’re on the latest Excel build with TAKE, this gets even cleaner:
=LET(
data, B2:INDEX(B:B, COUNTA(B:B)+1),
TAKE(data, -12)
)
Use this as the Refers to: formula in Name Manager. Anywhere you previously used MyLast12Months, the range now updates automatically without volatility.
Legacy pattern:
{=INDEX($D$2:$D$1000,
MATCH(1,
($A$2:$A$1000=H2)*($B$2:$B$1000=H3),
0)
)}
Problems:
Dynamic arrays make this pattern trivial.
=LET(
id, H2,
region, H3,
rows, FILTER($D$2:$D$1000, ($A$2:$A$1000=id)*($B$2:$B$1000=region)),
INDEX(rows, 1)
)
Or, if you’re fine with a spilled result (all matches):
=FILTER($D$2:$D$1000, ($A$2:$A$1000=H2)*($B$2:$B$1000=H3))
Benefits:
Old conditional sum with CSE:
{=SUM(($A$2:$A$1000=H2)*($B$2:$B$1000=H3)*$C$2:$C$1000)}
Modern alternative using SUMIFS where possible:
=SUMIFS($C$2:$C$1000, $A$2:$A$1000, H2, $B$2:$B$1000, H3)
If the logic is more complex (e.g., OR conditions), use LET + arrays:
=LET(
regionOK, ($A$2:$A$1000="North") + ($A$2:$A$1000="East"),
productOK, ($B$2:$B$1000="Widget"),
values, $C$2:$C$1000,
SUM(FILTER(values, (regionOK>0)*productOK))
)
No CSE, and the boolean logic is explicit.
INDIRECT is useful but volatile and fragile (breaks on sheet renames). Often you can avoid it.
Old pattern:
=SUM(INDIRECT(A1 & "[Amount]"))
Where A1 contains a table name like Sales2025.
Modern pattern: store actual references, not text.
2025, 2026)=Sales2025[Amount], =Sales2026[Amount])XLOOKUP to return the range directly.=LET(
year, H2,
rng, XLOOKUP(year, Map[Year], Map[Range]),
SUM(rng)
)
No INDIRECT, no volatility, and sheet renames are handled by Excel’s reference engine.
Old pattern:
=SUM(INDIRECT("Table1[" & H2 & "]"))
New pattern: CHOOSECOLS with MATCH.
=LET(
colName, H2,
tbl, Table1,
colIndex, MATCH(colName, DROP(Table1[#Headers],,0), 0),
SUM(CHOOSECOLS(tbl, colIndex))
)
You’re still dynamic, but the formula is non-volatile and strongly typed against the table.
LET isn’t just syntactic sugar. It:
Old, repeated logic:
=(Revenue - Cost - IF(Type="Promo", PromoCost, 0))/Revenue
Copied across thousands of rows with small variations.
New pattern with LET:
=LET(
revenue, [@Revenue],
cost, [@Cost],
promo, [@PromoCost],
type, [@Type],
adjCost, cost + IF(type="Promo", promo, 0),
margin, (revenue - adjCost) / revenue,
margin
)
Benefits:
adjCost or revenue to debug[@Revenue] or [@Cost] expressionsOld:
=SUM(FILTER(Sales[Amount], Sales[Region]="North")) /
SUM(FILTER(Sales[Amount], Sales[Region]<>"North"))
The same filter logic is repeated.
New:
=LET(
amt, Sales[Amount],
region, Sales[Region],
north, FILTER(amt, region="North"),
other, FILTER(amt, region<>"North"),
SUM(north) / SUM(other)
)
Cleaner, and potentially faster on large ranges.
LAMBDA lets you define custom functions without VBA, store them in Name Manager, and reuse them across the workbook.
Old pattern with OFFSET:
=AVERAGE(OFFSET(B2,COUNT(B:B)-N,0,N,1))
New LAMBDA function:
RollingAverage in Name Manager.=LAMBDA(range, periods,
LET(
n, ROWS(range),
lastN, DROP(range, n-periods),
AVERAGE(lastN)
)
)
Now call it like a native function:
=RollingAverage(B2:B1000, 12)
No volatility, and the logic is centralised.
Define a generic function FirstMatch:
=LAMBDA(criteriaRange1, criteriaValue1,
criteriaRange2, criteriaValue2,
returnRange,
LET(
mask, (criteriaRange1=criteriaValue1) * (criteriaRange2=criteriaValue2),
result, FILTER(returnRange, mask),
INDEX(result, 1)
)
)
Usage:
=FirstMatch(A:A, H2, B:B, H3, D:D)
You’ve effectively built a custom INDEX/MATCH variant without any VBA.
SalesByRegion, NormalizeRange, RollingAverage)Some volatile functions are unavoidable (NOW, TODAY), but you can control their blast radius.
Instead of sprinkling TODAY() everywhere:
=TODAY() in a single cell, e.g., Config!B2.LET to reference it:=LET(
today, Config!$B$2,
dueDate, [@DueDate],
MAX(0, dueDate - today)
)
This:
Config!B2 for testingFor sampling or random IDs, consider:
SEQUENCE) combined with hashing logicExample: pseudo-random but stable ID per row:
=LET(
key, [@CustomerID] & [@OrderDate],
hash, SUMPRODUCT(CODE(MID(key, SEQUENCE(LEN(key)), 1))),
TEXT(hash, "000000")
)
Not cryptographic, but stable and non-volatile.
You don’t need to rewrite everything at once. A pragmatic approach:
Formulas > Error Checking > Circular References to spot complex areasOFFSET(, INDIRECT(, and { (CSE) in formulasLET to name existing logic before changing itOFFSET formula in LET and then swap out the OFFSET for INDEX/TAKELET block 10 times, it’s a candidate=IF(ABS(old-new)<0.00001,"OK","CHECK") for numeric comparisonsThis keeps risk manageable while modernising the workbook.
Pick one of your existing workbooks that uses OFFSET or INDIRECT heavily and:
OFFSET(.OFFSET-based dynamic range with a LET + dynamic array pattern from this article.Do this once, properly, and you’ll have a reusable template for refactoring the rest of your models away from volatile hacks and into modern, robust Excel.
Excel Formulas
New
Next Batches Now Live
Power BI
SQL
Power Apps
Power Automate
Microsoft Fabrics
Azure Data Engineering