Excel formulas, explained properly
A practical guide to writing, reading and fixing spreadsheet formulas — from the basics through to dynamic arrays. Every example is interactive: click a cell to see its formula, step through nested logic, or paste your own formula into the formatter.
- Jump around. Use the sidebar, or press Ctrl+K and type a function name (e.g.
xlookup,textjoin). - Searching moves you through the guide. Every mention is highlighted; Enter jumps to the next one, Shift+Enter the previous, Esc clears. The sidebar becomes a map of results — each section shows how many matches it holds, sections with none are greyed out, and clicking one takes you straight to its first match.
- Tags: Basic everyone should know · 365 needs Microsoft 365 / Excel 2021+ · Legacy still works, but there's a better modern option · Advanced power-user territory.
- Grids with a green formula bar are live. Click any cell (or use arrow keys) to see its formula; the ranges it uses light up in matching colours.
- Copy buttons on every formula block give you a version you can paste straight into Excel.
Formula formatterPaste a monster formula, get it laid out readably REF
Evaluate FormulaExcel's own step-by-step debugger TOOL
XLOOKUP explorerSee what each match mode does REF
Every error explained#N/A, #REF!, #SPILL! and friends REF
Which lookup should I use?A one-minute decision flow REF
Keyboard shortcutsThe 30 worth learning
Formula fundamentals
Five minutes on the mechanics that every other chapter relies on: what the parts of a formula are called, how references behave when you copy a formula, and the handful of keystrokes that make editing painless.
Anatomy of a formula
Every formula starts with =. After that, Excel reads a mix of functions (named operations like SUM), references (cells or ranges like C2:C5), constants (numbers, "text in quotes", TRUE) and operators (+ - * / ^ &). Functions take arguments inside brackets, separated by commas.
=ROUND(SUM(C2:C5) * $F$1 / 1000, 2) & " tCO2e" // ROUND(...) function with two arguments: the value, and 2 decimal places // SUM(C2:C5) a function nested inside another function // $F$1 an absolute reference (see below) — won't move when copied // & " tCO2e" the join operator, glueing text onto the number
Click around this grid to see how a small emissions table is built. The dot in a cell's corner means it holds a formula; the ranges it uses light up.
Words you'll see throughout this handbook
- Range — a block of cells, written
top-left:bottom-right, e.g.B2:D9. A whole column isB:B. - Argument — one input to a function. Optional arguments are shown in [square brackets] in Excel's tooltip.
- Nesting — using a function's result as another function's argument:
ROUND(SUM(...), 2). - Array — several values handled at once, written
{1,2,3}(across) or{1;2;3}(down). Modern Excel returns arrays by "spilling" into neighbouring cells (chapter 7). - Volatile — a function that recalculates every time anything changes (
NOW,OFFSET,INDIRECT,RAND). Fine in small doses; slow in big workbooks.
Relative, absolute and mixed references Basic
When you copy a formula, Excel shifts every reference by the same distance you moved. A $ pins a column letter or row number so it doesn't move. This single idea is behind most "why did my formula break when I filled it down?" questions.
| Written as | Name | Copied one column right & one row down becomes | Use when |
|---|---|---|---|
A1 | Relative | B2 | Each copy should look at "its own" row/column — the default. |
$A$1 | Absolute | $A$1 | Everything should point at one fixed cell (a rate, a factor, a date). |
$A1 | Mixed (column locked) | $A2 | Always read from column A, but follow the row — e.g. a row label. |
A$1 | Mixed (row locked) | B$1 | Always read from row 1, but follow the column — e.g. a column header. |
Click inside a reference in the formula bar and press F4 repeatedly: A1 → $A$1 → A$1 → $A1 → A1. Much faster than typing $ by hand.
Reference shifter
Type a formula for cell B2, then see what it becomes when filled across and down.$Red parts are locked; blue parts move. The green cell is where you typed the formula.
The classic exercise: one formula in B2 that fills a whole multiplication grid. Click cells in different rows and columns to see how the same formula points at different inputs.
Common mistakes
- Forgetting to lock a lookup table.
=VLOOKUP(A2,F2:G20,2,0)filled down becomesF3:G21,F4:G22… and quietly loses the top rows. Write$F$2:$G$20(or use an Excel Table — chapter 6). - Locking too much.
$A$2*$B$1copied anywhere still multiplies A2 by B1. Only lock the part that should stay put. - Moving vs copying. Cutting and pasting (Ctrl+X) does not shift references — it keeps them pointing at the original cells. Copying (Ctrl+C) does shift them.
Order of operations
Excel evaluates operators in a fixed order. When in doubt, add brackets — they cost nothing and make intent obvious to the next reader.
| Priority | Operator | Meaning | Example |
|---|---|---|---|
| 1 | : , space | Reference operators (range, union, intersection) | SUM(A1:A5,C1:C5) |
| 2 | - (negation) | Minus sign in front of a value | =-2^2 gives 4 in Excel, not −4 — negation happens before the power |
| 3 | % | Percent | =50% → 0.5 |
| 4 | ^ | Exponent | =2^10 → 1024 |
| 5 | * / | Multiply, divide | =6/2*3 → 9 (left to right) |
| 6 | + - | Add, subtract | =2+3*4 → 14 |
| 7 | & | Join text | ="Total: "&2+3 → "Total: 5" |
| 8 | = <> < > <= >= | Comparison — returns TRUE/FALSE | =A1>=100 |
=A1>B1*2 works as you'd expect (multiply first, then compare). But =A1="Yes"&"No" is not comparing A1 to "YesNo" — the & runs first. And =A1=B1=C1 does not test that all three are equal; it compares A1=B1 (TRUE/FALSE) with C1. Use AND(A1=B1, B1=C1).
Editing shortcuts that pay for themselves
$ on the selected referenceF4Excel doesn't care about spaces or line breaks inside a formula. Press Alt+Enter in the formula bar to break after each argument — it's the single biggest readability win for complex formulas, and the formatter tool in chapter 2 will lay them out for you.
Reading & untangling complex formulas
Most "complex" formulas are just three or four simple ones nested together. The skill is knowing where to start reading, and having a tool that lays the formula out so the structure is visible.
The inside-out method
Excel evaluates the innermost brackets first and works outwards — so read the formula the same way. In practice:
- Find the deepest bracket pair. Click just after any
(or)in the formula bar and Excel briefly bolds its partner; brackets are also colour-coded by depth. - Ask what that innermost piece returns — a number? a position? a TRUE/FALSE? an array? Write it down if it helps.
- Replace it mentally with its result and move one level out. Repeat until you reach the
=. - Check with F9. Select any self-contained piece in the formula bar and press F9 to see its actual value. Press Esc afterwards or you'll hard-code the result!
The three walk-throughs below do exactly this, one step at a time. Use the buttons or the ← → keys.
Worked walk-throughs
1. A lookup wrapped in error handling
The most common pattern in any workbook: find a value in another sheet, and show a friendly message if it's missing. Cell B2 holds "Diesel"; the Factors sheet lists fuels in column A and factors in column C.
2. Conditional sum with boolean arithmetic
A SUMPRODUCT that adds up column D only where two conditions are both true. It looks intimidating because it works on whole arrays at once — but each step is simple. The data (8 rows) is: fuel in B, kWh in C, tonnes CO₂e in D.
3. Dynamic-array chain: FILTER → UNIQUE → TEXTJOIN 365
Modern formulas often pipe one array-returning function into the next. Read the chain from the inside: filter the list, remove duplicates, join into one string. Uses the same grid as above (site names in column A).
Formula formatter tool
Paste any formula and it will be laid out one argument per line, indented by nesting depth, with matching brackets colour-coded. Hover a bracket to see its partner. The output uses only spaces and line breaks, so Copy formatted gives you something you can paste straight back into Excel — it will still calculate.
Formula formatter
Nothing is calculated — it only re-arranges the text.Rules the formatter follows (so you can do it by hand)
- Short, simple calls such as
MATCH(A2,Names,0)stay on one line. Anything longer, or containing another function, is opened up. - Each argument of an opened-up function goes on its own line, indented one level deeper than the function name. The closing bracket lines up with the function name.
- Text in quotes is never split, even if it contains commas or brackets.
- Array constants
{1,2;3,4}and structured referencesTable[[#Headers],[Col]]are kept intact. - Depth colours: the bracket colour tells you how deep you are. If the closing bracket's colour doesn't match what you expected, you've found the missing bracket.
Naming the pieces with LET 365
When a formula repeats the same sub-expression, or is simply too long to read, LET lets you give each piece a name — like variables in code. It also makes the formula faster, because each named piece is calculated once.
LET 365 Advanced
LET(name1, value1, [name2, value2, …], calculation)
Names come in pairs (name, value) and the last argument is the calculation that uses them. Names can refer to earlier names.
=IF(XLOOKUP(B2,Factors[Fuel],Factors[kgCO2e])=0,
"check fuel",
C2*XLOOKUP(B2,Factors[Fuel],Factors[kgCO2e])/1000)=LET(
factor, XLOOKUP(B2, Factors[Fuel], Factors[kgCO2e]),
tonnes, C2 * factor / 1000,
IF(factor = 0, "check fuel", tonnes)
)Reading a LET: skim the names first (factor, tonnes) — they tell you the story. Then read the final line, which is usually short. Only dig into the value expressions if something looks wrong.
- Formulas → Evaluate Formula steps through a formula exactly like the walk-throughs above, one piece at a time. Ideal for someone else's formula.
- Formulas → Trace Precedents / Dependents draws arrows to the cells a formula uses (or the formulas that use this cell). Remove with Remove Arrows.
- F2 on a formula cell colours every reference and outlines the matching ranges on the sheet — the same thing the interactive grids in this handbook do.
- Ctrl+` flips the whole sheet to show formulas instead of values, so you can spot the one hard-coded number in a column of formulas.
Text & dates
Reshaping the value itself: splitting site codes, cleaning up imports, building labels, and working with dates — which Excel stores as plain numbers, a fact behind most date oddities.
Text functions
A realistic clean-up job: site codes exported from another system as one messy string. Click through the formula cells.
TEXT Basic
TEXT(value, format_code)
Turns a number into text using a format code — the same codes you can type into Format Cells (Ctrl+1) → Custom, such as #,##0.0 or d mmm yyyy. Essential when building labels with &, because & ignores cell formatting.
="Total: "&TEXT(E6,"#,##0.0")&" tCO2e as at "&TEXT(TODAY(),"d mmm yyyy") → Total: 72.6 tCO2e as at 18 Sep 2026
The result is text — you can't sum it. Keep the number in its own cell and only use TEXT for the label.
LEFT · RIGHT · MID Basic
LEFT(text, [num_chars]) · RIGHT(text, [num_chars]) · MID(text, start, num_chars)
Take characters from the start, end, or middle (starting at position start, counting from 1).
=LEFT("OX-01 Oxford Office", 5) → OX-01
=RIGHT("2026-09", 2) → 09
=MID("OX-01 Oxford Office", 4, 2) → 01
LEN Basic
LEN(text)
Number of characters, including spaces. Handy for validation (=LEN(A2)=8) and for the classic "count how many times X appears" trick:
=LEN(A2)-LEN(SUBSTITUTE(A2,",","")) // number of commas in A2
FIND · SEARCH Basic
FIND(find_text, within_text, [start]) · SEARCH(same arguments)
Return the position of one string inside another. FIND is case-sensitive and literal; SEARCH ignores case and allows wildcards (* ?). Both return #VALUE! if not found — wrap with ISNUMBER to make a test:
=ISNUMBER(SEARCH("gas", B2)) → TRUE if B2 contains "gas" in any case
=MID(A2, FIND(" ", A2)+1, 99) // everything after the first space (legacy way)
SUBSTITUTE · REPLACE
SUBSTITUTE(text, old_text, new_text, [instance]) · REPLACE(text, start, num_chars, new_text)
SUBSTITUTE swaps text you know (all instances, or just the n-th). REPLACE swaps by position. Nine times out of ten you want SUBSTITUTE.
=SUBSTITUTE(A2, "-", "") → OX01 Oxford Office =SUBSTITUTE(A2, " ", "_", 1) → OX-01_Oxford Office (only the first space)
TRIM · CLEAN Basic
TRIM(text) · CLEAN(text)
The first thing to reach for when a lookup "should" match but doesn't. TRIM removes leading/trailing spaces and collapses doubles; CLEAN strips non-printing characters from imports. They don't remove non-breaking spaces (CHAR(160)) from web copy-paste — add SUBSTITUTE(x, CHAR(160), " ") for those.
=TRIM(CLEAN(SUBSTITUTE(A2, CHAR(160), " "))) // the full "sanitise an import" wrapper
UPPER · LOWER · PROPER Basic
UPPER(text) · LOWER(text) · PROPER(text)
Change case. PROPER capitalises every word (so "o'neil" becomes "O'Neil" and "LPG" becomes "Lpg" — check your acronyms). Excel comparisons with = are already case-insensitive, so you rarely need these for matching.
& · CONCAT · CONCATENATE Basic
text1 & text2 · CONCAT(text1, [text2, …])
Join pieces of text. The & operator is the clearest for a few pieces; CONCAT accepts whole ranges. CONCATENATE is the old name, kept for compatibility.
=A2&" – "&B2&" ("&TEXT(C2,"#,##0")&" kWh)"
→ Oxford office – Electricity (48,200 kWh)
TEXTJOIN 365
TEXTJOIN(delimiter, ignore_empty, text1, [text2, …])
Join a whole range with a separator between items — the function everyone wanted for 20 years. Pair it with FILTER or UNIQUE to build "list all the sites in this region" strings.
=TEXTJOIN(", ", TRUE, A2:A5) → Oxford office, Oxford office, Madrid office, Warehouse
=TEXTJOIN(", ", TRUE, UNIQUE(A2:A5)) → Oxford office, Madrid office, Warehouse
TEXTSPLIT 365
TEXTSPLIT(text, col_delimiter, [row_delimiter], [ignore_empty], [match_mode], [pad_with])
The formula version of Text-to-Columns. Splits into columns (and optionally rows) and spills the result.
=TEXTSPLIT("OX-01 Oxford Office", " ") → OX-01 | Oxford | Office (3 cells across)
=TEXTSPLIT("a,b;c,d", ",", ";") → a b / c d (2 × 2 block)
TEXTBEFORE · TEXTAFTER 365
TEXTBEFORE(text, delimiter, [instance_num], [match_mode], [match_end], [if_not_found]) · TEXTAFTER(same)
Replace the old LEFT(A2,FIND(" ",A2)-1) gymnastics. A negative instance_num counts from the end.
=TEXTBEFORE(A2, " ") → OX-01 =TEXTAFTER(A2, " ") → Oxford Office =TEXTAFTER(A2, " ", -1) → Office (after the LAST space) =TEXTBEFORE(A2, "-", , , , "no dash") // fallback instead of #N/A
VALUE · NUMBERVALUE
VALUE(text) · NUMBERVALUE(text, [decimal_sep], [group_sep])
Convert text that looks like a number into a real number (green-triangle "number stored as text" cells won't sum until you do). NUMBERVALUE handles European "1.234,5". Quick alternatives: --A2, A2*1, or Data → Text to Columns → Finish on the whole column.
REPT
REPT(text, number_times)
Repeat text. The classic use is an in-cell bar chart: =REPT("█", ROUND(E2/5,0)). Also for padding codes: =REPT("0", 6-LEN(A2))&A2 (or just use the 000000 number format).
Common mistakes with text
- Numbers that are secretly text. They left-align, get a green triangle, and are ignored by SUM. Fix with VALUE /
--/ Text to Columns, and check the source if it keeps happening. - Invisible characters. Two cells look identical but
=A1=B1says FALSE. CompareLENs; useCODE(MID(A1,n,1))to find the culprit; fix with the TRIM/CLEAN/SUBSTITUTE wrapper above. - Dates as text. "18/09/2026" typed in a text-formatted cell isn't a date.
DATEVALUEconverts it — but check day/month order. - Leading zeros vanishing. Postcodes and IDs like 007 lose zeros the moment they become numbers. Store as text, or use the
000number format.
Dates are just numbers
Excel stores a date as the number of days since 1 January 1900 (18 Sep 2026 is 46,283); a time is the fraction of a day (12:00 is 0.5). That's why you can subtract dates to get a day count, add 7 to move a week on, and why a date formatted as a number suddenly looks like 46283.
DATE · TODAY · YEAR / MONTH / DAY Basic
DATE(year, month, day) · TODAY() · YEAR(date)
Build a date from parts, or pull parts out. DATE is forgiving: DATE(2026,13,1) is 1 Jan 2027 and DATE(2026,3,0) is the last day of February — a neat trick for "last day of previous month".
=DATE(YEAR(A2), 1, 1) // first day of A2's year =DATE(YEAR(A2), MONTH(A2)+1, 0) // last day of A2's month (or use EOMONTH)
EOMONTH · EDATE Basic
EOMONTH(start_date, months) · EDATE(start_date, months)
Move whole months forward or back. EOMONTH lands on the month end; EDATE keeps the same day. Negative months go backwards; 0 means "this month".
=EOMONTH(A2, 0) // end of A2's month → 30/09/2026 =EOMONTH(A2, -1)+1 // first day of A2's month → 01/09/2026 =EDATE(A2, 12) // one year on, same day → 18/09/2027
NETWORKDAYS · WORKDAY
NETWORKDAYS(start, end, [holidays]) · WORKDAY(start, days, [holidays])
Count working days between two dates, or find the date n working days away. Point holidays at a list of bank-holiday dates. The .INTL versions let you choose which days count as the weekend.
DATEDIF Hidden
DATEDIF(start, end, "Y" | "M" | "D" | "YM" | "MD" | "YD")
Whole years/months/days between two dates. It's not in Excel's autocomplete (a compatibility leftover) but it works and it's the cleanest way to get "age in years" or "months elapsed".
=DATEDIF(A2, TODAY(), "M") // whole months since A2
- UK vs US order. A CSV with 03/04/2026 may be imported as 4 March. Check with
=TEXT(A2,"d mmmm yyyy")and fix at import (Data → From Text/CSV lets you set the locale). - Dates with times. A "date" of 46283.6 has a time component;
=A2=DATE(2026,9,18)fails. CompareINT(A2), or use>=and<the next day in SUMIFS. - Text dates in criteria. In SUMIFS write
">="&DATE(2026,1,1), not">=01/01/2026"— the latter depends on regional settings.
Logic & conditionals
Making a formula decide. The IF family covers "if this then that"; AND/OR combine tests; and the boolean-maths trick turns TRUE/FALSE into 1/0 so you can count and filter without any IF at all.
IF, IFS and SWITCH
IF Basic
IF(logical_test, value_if_true, [value_if_false])
Test something; return one value if it's TRUE, another if FALSE. Leaving out the third argument returns FALSE (usually not what you want — use "" for blank).
=IF(C2>50000, "Large", "Small") =IF(C2="", "", C2*D2/1000) // stay blank until there's an input =IF(AND(B2="Natural gas", C2>50000), C2*0.18293/1000, 0)
IFS 2019+
IFS(test1, value1, [test2, value2, …])
Several tests in order; returns the value for the first test that's TRUE. Replaces nested IFs. There's no built-in "else" — add a final TRUE, "otherwise" pair, or you'll get #N/A when nothing matches.
=IF(C2>=100000,"A",IF(C2>=50000,"B",IF(C2>=10000,"C","D")))
=IFS(
C2 >= 100000, "A",
C2 >= 50000, "B",
C2 >= 10000, "C",
TRUE, "D"
)Order matters. Tests are checked top to bottom, so put the strictest first (≥100000 before ≥50000). For banding with many tiers, a lookup table with XLOOKUP(…, -1) is cleaner still — see the XLOOKUP explorer.
SWITCH 2019+
SWITCH(expression, value1, result1, [value2, result2, …], [default])
When you're comparing one thing against a list of exact values, SWITCH is tidier than IFS because you write the expression once.
=SWITCH(B2, "Natural gas", "kWh", "Diesel", "litre", "Electricity", "kWh", "check unit")
AND, OR, NOT and boolean maths
AND · OR · NOT Basic
AND(test1, [test2, …]) · OR(test1, [test2, …]) · NOT(test)
AND is TRUE only if every test is TRUE; OR if any is; NOT flips. They return a single TRUE/FALSE, which is why they don't work inside array formulas like SUMPRODUCT or FILTER — there you multiply instead (below).
=IF(AND(C2>0, D2>0), C2*D2, "missing input") =IF(OR(B2="Diesel", B2="Petrol"), "liquid fuel", "other") =NOT(ISBLANK(A2))
Boolean maths: * for AND, + for OR, -- to convert Advanced
Excel treats TRUE as 1 and FALSE as 0 the moment you do arithmetic on it. So:
| You write | It means | Why |
|---|---|---|
(B2:B9="Natural gas")*(C2:C9>50000) | AND, row by row | 1×1 = 1 only when both are TRUE |
(B2:B9="Diesel")+(B2:B9="Petrol") | OR, row by row | 0+1 or 1+0 gives 1 (both TRUE would give 2 — fine for FILTER, wrong for counting) |
--(A2:A9<>"") | Convert TRUE/FALSE to 1/0 | Two minus signs: negate, then negate back. *1 or N() do the same. |
SUMPRODUCT(--(A2:A9>0)) | Count of positives | Sums the 1s |
This is the engine under SUMPRODUCT and the include argument of FILTER. Once you can read (cond1)*(cond2) as "cond1 AND cond2", most scary array formulas become plain English.
ISBLANK · ISNUMBER · ISTEXT · ISERROR · ISNA
ISNUMBER(value) etc.
Tests that return TRUE/FALSE about what a cell contains — useful as the test inside IF, or to build a "contains" check: =ISNUMBER(SEARCH("gas",B2)). Note ISBLANK is FALSE for a cell containing "" from a formula; use A2="" to catch both.
IFERROR vs IFNA — handle errors on purpose
IFERROR · IFNA Basic
IFERROR(value, value_if_error) · IFNA(value, value_if_na)
Both return value normally and a fallback if it errors. The difference is what they catch:
IFNA — catches only #N/A"Not found" from lookups. Every other error (#REF!, #VALUE!, #DIV/0!) still shows, so genuine bugs stay visible. Prefer this for lookups.
IFERROR — catches everythingIncluding the error that tells you a column was deleted or a number is text. Use it only when you're sure any error should be silenced.
=IFNA(XLOOKUP(A2, Factors[Fuel], Factors[Factor]), "check fuel name") =IFERROR(C2/D2, 0) // OK: the only realistic error is divide-by-zero
Common mistakes with logic
- Wrapping everything in IFERROR(…, 0). A broken reference now silently reports zero and your totals are quietly wrong. Return a visible message instead, or use IFNA.
- Comparing to text numbers.
=IF(A2="10",…)compares with the text "10". Write=IF(A2=10,…). - Returning "" then doing maths. A cell showing blank from
IF(x,"",…)is text;=B2+1gives#VALUE!. Use SUM (which ignores text) or return 0 and hide it with the0;-0;"-"number format. - IFS with no default. Ends in
#N/Afor unexpected inputs. AddTRUE, "unexpected"as the last pair.
Lookup & reference
"Find this fuel in the factors table and give me its factor." This chapter starts with the way most of us learned it (VLOOKUP), shows the more robust classic (INDEX/MATCH), then makes the case for XLOOKUP — which does everything both do, with fewer traps.
VLOOKUP — the one everyone learned first Legacy
VLOOKUP Basic Legacy
VLOOKUP(lookup_value, table_array, col_index_num, [range_lookup])
Looks for lookup_value down the first column of table_array, then returns the value from column number col_index_num of that table (counting the first column as 1). The fourth argument must be FALSE (or 0) for an exact match — always set it; the default is approximate matching, which returns wrong answers silently on unsorted data.
=VLOOKUP(A2, $F$2:$H$6, 3, FALSE)
// A2 what to find ("Diesel")
// $F$2:$H$6 the table, locked so it doesn't shift when filled down
// 3 return the 3rd column of that table (Factor)
// FALSE exact match — never leave this out
Why VLOOKUP causes so many problems
- It can only look right. The key must be in the first column of the table. If the fuel name is in column H and the factor in column F, you're stuck (or you re-arrange the source data).
- The column number is hard-coded. Insert a column inside the table and
3now points at the wrong thing — no error, just wrong numbers. - Approximate match by default. Forget
FALSEand "Kerosene" may return Diesel's factor because it's alphabetically nearby. - Unlocked ranges.
F2:H6filled down becomesF3:H7and the top row drops out of the lookup. - Slow on big data. Returning several columns means several VLOOKUPs, each scanning the whole table.
HLOOKUP Legacy
HLOOKUP(lookup_value, table_array, row_index_num, [range_lookup])
VLOOKUP rotated 90°: searches the first row and returns from a row number below. Same limitations. XLOOKUP handles both directions.
INDEX & MATCH — the robust classic
Two small functions that, combined, fix every VLOOKUP problem above. MATCH finds the position of a value in a single row or column. INDEX returns the value at a given position in a range. Chain them: "find which row Diesel is on, then return that row of the Factor column".
MATCH Basic
MATCH(lookup_value, lookup_array, [match_type])
Returns a position (1, 2, 3…), not a value. match_type 0 = exact (use this), 1 = largest value ≤ lookup (list sorted ascending), -1 = smallest value ≥ lookup (sorted descending).
=MATCH("Diesel", F2:F6, 0) → 2 // Diesel is the 2nd item
INDEX Basic
INDEX(array, row_num, [column_num])
Returns the cell at that row (and column) of the range. Row 0 or column 0 means "the whole row/column", which is how INDEX can return a range for other functions.
=INDEX(H2:H6, 2) → 2.51233 // 2nd cell of the Factor column =INDEX(G2:I4, 2, 3) → value at row 2, column 3 of the block
INDEX / MATCH together, and the two-way lookup Advanced
=INDEX($H$2:$H$6, MATCH(A2, $F$2:$F$6, 0)) // read it inside-out: MATCH finds the row → INDEX returns that row of column H // key column and return column are separate, so the key can be ANYWHERE — left, right, another sheet
Use two MATCHes for a grid where you need a row and a column — here, emission factors by fuel (rows) and year (columns):
=INDEX($G$2:$I$4, MATCH(A2, $F$2:$F$4, 0), MATCH(B2, $G$1:$I$1, 0)) // INDEX(block, which row, which column)
Inserting columns in the source table no longer breaks anything, because the return column is a reference, not a number. This pattern is still worth knowing: it's everywhere in existing workbooks, and it works in every Excel version.
XLOOKUP — the modern replacement 365
XLOOKUP is INDEX/MATCH in a single function, with a built-in "if not found", the ability to return several columns at once, and search-direction and match-mode options. If your whole team is on Microsoft 365, it should be your default.
XLOOKUP 365 Basic
XLOOKUP(lookup_value, lookup_array, return_array, [if_not_found], [match_mode], [search_mode])
| Argument | What it is | Values |
|---|---|---|
lookup_value | What to find | A cell, text, number, or an array of several |
lookup_array | Where to search (one row or one column) | e.g. Factors[Fuel] |
return_array | What to return — same height as lookup_array | One column, or several (Factors[[Unit]:[Factor]]) to spill them all |
if_not_found | Value to show instead of #N/A | "check name", 0, "" |
match_mode | How to match | 0 exact (default) · -1 exact or next smaller · 1 exact or next larger · 2 wildcard |
search_mode | Where to start | 1 first to last (default) · -1 last to first · 2/-2 binary search (sorted data only) |
=XLOOKUP(A2, $F$2:$F$6, $H$2:$H$6, "not found") =XLOOKUP(A2, Factors[Fuel], Factors[Factor], "not found") // same, using a Table =XLOOKUP(A2, Factors[Fuel], Factors[[Unit]:[Factor]], "") // returns Unit AND Factor, spilling across =XLOOKUP(A2, Factors[Fuel], Factors[Factor], , , -1) // last match (e.g. latest entry in a log)
The same job, three ways
Find the factor for the fuel in A2 from a table with Fuel in F and Factor in H:
=VLOOKUP(A2,$F$2:$H$6,3,FALSE)
Key must be first column; 3 is fragile; no not-found handling.
=INDEX($H$2:$H$6,MATCH(A2,$F$2:$F$6,0))
Robust and universal, but two functions to read.
=XLOOKUP(A2,$F$2:$F$6,$H$2:$H$6,"not found")
Reads left to right: find this, here, return that.
| Capability | VLOOKUP | INDEX / MATCH | XLOOKUP |
|---|---|---|---|
| Look left of the key column | No | Yes | Yes |
| Survives inserting columns in the table | No | Yes | Yes |
| Exact match by default | No | No | Yes |
| Built-in "if not found" | No | No | Yes |
| Return several columns at once | No | Partly | Yes |
| Search from the bottom (last match) | No | No | Yes |
| Next-smaller / next-larger banding | Sorted only | Sorted only | Any order |
| Horizontal lookup | HLOOKUP | Yes | Yes |
| Works in Excel 2016 / 2019 | Yes | Yes | No |
XLOOKUP explorer
Change the lookup value and the two mode arguments to see what XLOOKUP returns and why. Type a fuel name (try a wildcard like *gas with match mode 2), or a number to switch to a banding table where the "next smaller" mode shines.
Try XLOOKUP's modes
Real XLOOKUP logic running on two small tables.Other reference functions
XMATCH 365
XMATCH(lookup_value, lookup_array, [match_mode], [search_mode])
MATCH with XLOOKUP's mode arguments and exact-match default. Use with INDEX when you need a position (e.g. to feed into OFFSET or to find the last non-blank row: =XMATCH(TRUE, A:A<>"", , -1)).
Approximate matching for bands and tiers
When you have thresholds (0, 50, 100, 250, 500 → Band A–E), don't write nested IFs. Put the lower bounds in a column and use "exact or next smaller":
=XLOOKUP(C2, Bands[Threshold], Bands[Band], "n/a", -1) // 365: works in any order =VLOOKUP(C2, $F$2:$G$6, 2, TRUE) // legacy: thresholds must be sorted ascending
INDIRECT Advanced
INDIRECT(ref_text, [a1])
Turns text into a reference: =INDIRECT("'"&A2&"'!B5") reads B5 from whichever sheet is named in A2. Powerful for "one summary row per sheet" layouts. Costs: it's volatile (recalculates constantly), it breaks silently if a sheet is renamed, and Excel can't trace it. Use sparingly; consider a Table plus XLOOKUP, or Power Query, instead.
OFFSET Advanced
OFFSET(reference, rows, cols, [height], [width])
Returns a range that's shifted from a starting cell and optionally resized. Classic for "the last 12 months": =SUM(OFFSET(B1, COUNTA(B:B)-12, 0, 12, 1)). Also volatile. In 365, TAKE/DROP (=SUM(TAKE(B2:B200,-12))) do the same job without the volatility.
CHOOSE · ROW · COLUMN
CHOOSE(index_num, value1, [value2, …]) · ROW([ref]) · COLUMN([ref])
CHOOSE(3,"Q1","Q2","Q3","Q4") returns "Q3". ROW() and COLUMN() return the current row/column number — handy for alternating formats or building sequences in older Excel (365 has SEQUENCE).
Referring to other sheets and workbooks
=Factors!B2 // sheet named Factors, cell B2 ='Site data 2026'!B2 // sheet names with spaces need single quotes =SUM(Jan:Dec!B2) // 3-D reference: B2 on every sheet from Jan to Dec ='C:\Reports\[Factors 2026.xlsx]Factors'!$B$2 // another workbook (a "workbook link", see chapter 6)
Tip: instead of typing these, start the formula, then click the other sheet tab and the cell — Excel writes the reference correctly, quotes and all.
Tables, PivotTables, names & links
A short introduction to four features that make workbooks more robust: Excel Tables (ranges that know their own size), PivotTables (summaries without formulas), named ranges, and links between workbooks.
Excel Tables and structured references Basic
Select your data and press Ctrl+T. The range becomes a Table: it gets a name (rename it on the Table Design tab — "Table1" is not a name), filter buttons, banded rows, and it grows automatically when you add rows. Formulas that refer to the table pick up new rows with no editing.
Inside formulas you refer to a Table by name and column instead of cell addresses. These are called structured references:
| Syntax | Refers to | Typical use |
|---|---|---|
Energy | All data rows (no header, no total) | =ROWS(Energy) |
Energy[kWh] | The data cells of one column | =SUM(Energy[kWh]) |
[@kWh] | This row's value in that column (only inside the table) | =[@kWh]*[@Factor]/1000 |
Energy[@] | The whole current row | rare |
Energy[[Fuel]:[kWh]] | Several adjacent columns | =XLOOKUP(x, Energy[Site], Energy[[Fuel]:[kWh]]) |
Energy[[#Headers],[kWh]] | The header cell | labels, MATCH across headers |
Energy[#Headers] | All header cells | =MATCH("kWh", Energy[#Headers], 0) |
Energy[#Totals] · Energy[[#Totals],[kWh]] | The total row (if switched on) | referencing the visible total |
Energy[#All] | Everything including headers and totals | charts, copying |
Energy[#Data] | Same as Energy | explicitness |
Click through the formulas in and around this Table. Notice the formula in E2 is identical in every row — Excel calls this a calculated column; type it once and it fills the whole column, new rows included.
- No more
$A$2:$A$500that quietly stops at row 500 when the data reaches 600. - Formulas read like sentences:
SUMIFS(Energy[tCO2e], Energy[Fuel], "Natural gas"). - PivotTables and charts built on a Table refresh to include new rows.
- Column headers stay visible when you scroll (they replace the A/B/C letters).
- Caveat: Tables can't be used with merged cells or with sheet-level array formulas that need to spill into the Table. Keep spilling formulas outside.
PivotTables & PivotCharts
A PivotTable summarises a list — totals, counts, averages — by dragging fields into four zones, with no formulas. If you find yourself writing a grid of SUMIFS for every site × every fuel × every month, that's a PivotTable. Select your data (ideally a Table) and go to Insert → PivotTable.
| Zone | What goes there | Becomes |
|---|---|---|
| Rows | Categories you want down the side (Site, Fuel, Month) | One row per distinct value; nest several for a hierarchy |
| Columns | Categories across the top | One column per distinct value |
| Values | Numbers to aggregate (kWh, tCO2e) | Sum by default; right-click → Summarize Values By for Count, Average, Max… |
| Filters | Fields to slice the whole table by | A dropdown above the table. Slicers (Insert → Slicer) are the nicer, clickable version. |
- It doesn't update itself. After the source changes, right-click → Refresh (or Alt+F5). Build it on a Table so new rows are included.
- Dates group automatically into Years / Quarters / Months. Right-click a date → Group to change.
- Show Values As (right-click a value) gives % of total, difference from previous, running total — no formulas needed.
- PivotChart (PivotTable Analyze → PivotChart) is a chart wired to the pivot: filter the pivot, the chart follows.
- GETPIVOTDATA. Clicking a pivot cell from another formula writes
=GETPIVOTDATA("tCO2e",$A$3,"Site","Oxford"). It's robust to the pivot re-arranging. If you'd rather get a plain=B5, switch it off: PivotTable Analyze → Options ▾ → untick Generate GetPivotData. - Blank cells in a Values column silently turn Sum into Count. Check the source column is all numbers.
Name Manager — naming cells and ranges
A defined name is a label for a cell, a range or a constant. =C2*ElecFactor/1000 reads better than =C2*Factors!$D$7/1000, and if the factor moves, you update the name in one place. Create one by selecting the cell and typing in the Name Box (left of the formula bar), or via Formulas → Define Name. Manage them all with Ctrl+F3.
| Name | Refers to | Use |
|---|---|---|
ElecFactor | =Factors!$D$7 | =[@kWh]*ElecFactor/1000 |
ReportYear | =2026 (a constant — no cell at all) | =SUMIFS(…, Energy[Year], ReportYear) |
FuelList | =Factors[Fuel] | Source for a Data Validation dropdown |
kgToTonnes | =LAMBDA(kg, kg/1000) | A custom function: =kgToTonnes(B2) (365) |
- Names can't contain spaces or look like a cell reference (
Q1,FY2026are invalid — useQtr1,FY_2026). - Scope: workbook-scoped (default, usable anywhere) or sheet-scoped (only on that sheet; lets two sheets have their own
Total). Set it when creating; it can't be changed afterwards. - Press F3 while editing a formula to paste a name from a list.
- Name Manager's filter shows names with errors (
#REF!) — leftovers from deleted sheets that slow files down. Delete them. - Data Validation lists and Conditional Formatting rules accept names; that's often the cleanest way to reference a Table from those dialogs.
Workbook links (external references)
A formula can point into another workbook. While that file is open the reference looks like =[Factors 2026.xlsx]Factors!$D$7; once closed, Excel stores the full path:
='C:\Data\Emission factors\[Factors 2026.xlsx]Factors'!$D$7 ='https://aether.sharepoint.com/sites/Data/[Factors 2026.xlsx]Factors'!$D$7 // SharePoint / OneDrive
| Situation | What you'll see | What to do |
|---|---|---|
| Opening a file with links | "This workbook contains links to external sources" security bar | Enable Content if you trust it; values update from the source file (if reachable) |
| Source file moved or renamed | "We can't update some of the links" · values freeze at last saved | Data → Edit Links → Change Source to point at the new location |
| Source deleted or unavailable | Same warning; #REF! if the referenced sheet is gone | Edit Links → Break Link converts formulas to their last values (irreversible — copy first) |
| Finding hidden links | Warning appears but you can't see any link formulas | Check: defined names (Ctrl+F3), data validation lists, conditional formatting, chart series, and Ctrl+F for [ in formulas |
Links are convenient for a single shared factors file, but they break when files move, they're slow over SharePoint, and they make a workbook hard to send outside. For anything bigger than one or two cells, prefer Data → Get Data (Power Query) to pull the source table in cleanly, or paste values with a dated note of where they came from.
Data analysis & dynamic arrays
Summing and counting with conditions is the bread and butter of any reporting sheet. Then the modern part: dynamic-array functions that return whole lists — filtered, sorted, de-duplicated — from a single formula.
SUMIFS, COUNTIFS, AVERAGEIFS, MAXIFS, MINIFS Basic
All five share one shape: the range to aggregate first, then pairs of (criteria range, criterion). Every pair must be TRUE for a row to count (an AND). Click through the summary formulas on the right of this grid.
SUMIFS · COUNTIFS · AVERAGEIFS · MAXIFS · MINIFS Basic
SUMIFS(sum_range, criteria_range1, criteria1, [criteria_range2, criteria2, …]) · COUNTIFS(criteria_range1, criteria1, …)
Use the plural …IFS versions even for one condition — the argument order is consistent and you can add conditions later. (SUMIF/COUNTIF are the older singular forms with the sum range last.)
=SUMIFS(Energy[kWh], Energy[Site], "Oxford office", Energy[Fuel], "Electricity") =COUNTIFS(Energy[Fuel], "Natural gas", Energy[kWh], ">10000") =AVERAGEIFS(Energy[kWh], Energy[Site], F2) =MAXIFS(Energy[kWh], Energy[Fuel], "Natural gas")
Criteria syntax — the part people get wrong
Criteria are text strings, even when they compare numbers. Anything other than "equals this cell" needs quotes, and cell references are glued on with &:
| Criterion | Matches rows where the criteria cell is… |
|---|---|
"Diesel" or F2 | equal to Diesel (case-insensitive) |
"<>Diesel" | not Diesel |
">10000" | greater than 10000 |
">="&G2 | ≥ the value in G2 (note the &) |
">="&DATE(2026,1,1) | on or after 1 Jan 2026 — the safe way to write dates |
"*gas*" | contains "gas" (* = any characters, ? = one character) |
"OX-??" | OX- followed by exactly two characters |
"<>" | not blank |
"" | blank |
"~*" | a literal asterisk (tilde escapes wildcards) |
OR conditions aren't supported directly. Either add two SUMIFS together, or pass an array: =SUM(SUMIFS(D:D, C:C, {"Diesel","Petrol"})).
SUMPRODUCT and SUBTOTAL
SUMPRODUCT Advanced
SUMPRODUCT(array1, [array2, …])
Multiplies arrays element by element, then sums. Two jobs: (1) weighted sums in one go — =SUMPRODUCT(C2:C9, D2:D9) is Σ(kWh × factor) without a helper column; (2) conditional sums with logic SUMIFS can't express (OR across columns, conditions on calculated values, year of a date).
=SUMPRODUCT(Energy[kWh], Energy[Factor]) / 1000 // total tonnes, no helper column =SUMPRODUCT((YEAR(Energy[Date])=2026) * (Energy[Fuel]="Natural gas") * Energy[kWh]) // (condition) * (condition) * values — see Boolean maths in chapter 4
Reading tip: each (…) in brackets is a column of TRUE/FALSE; the * turns them into 1/0 and combines them with AND. Step through the walk-through in chapter 2.
SUBTOTAL · AGGREGATE
SUBTOTAL(function_num, ref1, …) · AGGREGATE(function_num, options, ref1, …)
SUBTOTAL aggregates only the visible rows — so a total above a filtered Table changes as you filter. function_num 9 = SUM, 1 = AVERAGE, 3 = COUNTA; add 100 (109, 101, 103) to also ignore manually hidden rows. It also ignores other SUBTOTALs, so nested subtotals don't double count. The Table "Total Row" uses it.
AGGREGATE is SUBTOTAL's big sibling: 19 functions and an options code — 6 = ignore errors, which makes =AGGREGATE(9, 6, D2:D500) a "sum, skipping the #N/As" without touching the errors.
Dynamic arrays: formulas that return lists 365
In Microsoft 365, a formula that produces several values spills them into the cells below and/or to the right. You write one formula, in one cell; Excel draws a thin blue border around the results. Only the top-left cell holds the formula — the rest are read-only ghosts. Refer to the whole spilled range with a #: =COUNTA(E2#).
FILTER 365
FILTER(array, include, [if_empty])
Returns the rows of array where include is TRUE. include is a column of TRUE/FALSE — combine tests with * (AND) and + (OR). Always set if_empty or you'll get #CALC! when nothing matches.
=FILTER(A2:C9, B2:B9="Natural gas", "none") =FILTER(Energy, (Energy[Fuel]="Natural gas") * (Energy[kWh]>50000), "none") =FILTER(Energy, (Energy[Site]="Oxford office") + (Energy[Site]="Lab"), "none") // OR
SORT · SORTBY 365
SORT(array, [sort_index], [sort_order], [by_col]) · SORTBY(array, by_array1, [order1, …])
SORT orders by a column inside the array (index number, 1 = ascending, -1 = descending). SORTBY orders by any other range, and can take several keys.
=SORT(FILTER(A2:C9, B2:B9="Natural gas"), 3, -1) // gas rows, biggest kWh first =SORTBY(Energy[Site], Energy[tCO2e], -1) // sites ordered by emissions
UNIQUE 365
UNIQUE(array, [by_col], [exactly_once])
The distinct values, in order of first appearance. Feed it to SORT for an alphabetical list, or to COUNTA to count distinct items. exactly_once = TRUE returns only the values that appear a single time.
=SORT(UNIQUE(Energy[Site])) =COUNTA(UNIQUE(Energy[Site])) // how many distinct sites
SEQUENCE 365
SEQUENCE(rows, [columns], [start], [step])
Generates numbers. =SEQUENCE(12) → 1 to 12 down a column; =EDATE(DATE(2026,1,1), SEQUENCE(12,1,0)) → the 12 month-start dates of 2026 — the usual way to build a report header that never needs retyping.
TAKE · DROP · CHOOSECOLS · VSTACK / HSTACK 365 Newer
Array "plumbing": TAKE(x, -12) the last 12 rows; DROP(x, 1) skip the header; CHOOSECOLS(x, 1, 3) pick columns; VSTACK(a, b) append one list under another (e.g. combine two sites' Tables into one). They replace most OFFSET and INDEX-with-SEQUENCE tricks.
GROUPBY · PIVOTBY 365 Newest
GROUPBY(row_fields, values, function, […]) · PIVOTBY(row_fields, col_fields, values, function, […])
A PivotTable as a formula — it updates live, no Refresh. =GROUPBY(Energy[Site], Energy[tCO2e], SUM) gives a site total list; PIVOTBY adds column fields. Rolling out across 365 channels during 2024–25; check your version has it before building on it.
#SPILL! and other dynamic-array gotchas
- #SPILL! means something is in the way — a value, a merged cell, or an Excel Table — where the results need to go. Click the cell and Excel outlines the blocked area.
- Spilled formulas can't live inside a Table. Put them beside it.
- @ appears in old formulas. When a pre-365 workbook opens, Excel adds
@(implicit intersection) where a formula would now spill. It's preserving the old behaviour; remove the @ only if you want spilling. - Colleagues on older Excel see spilled results as static values and
_xlfn.FILTERin the formula bar. Fine for reading, broken for editing. - Whole-column references (
A:A) inside FILTER/UNIQUE work but are slow — they process a million rows. Use a Table.
LET, LAMBDA and the helper functions Advanced
LET was covered in chapter 2. LAMBDA goes one step further and turns a formula into a reusable function with a name.
LAMBDA 365 Advanced
LAMBDA([parameter1, …], calculation)
Define it in Name Manager (Ctrl+F3 → New → Refers to) and call it by name anywhere in the workbook:
// Name: TONNES Refers to: =LAMBDA(qty, fuel, qty * XLOOKUP(fuel, Factors[Fuel], Factors[Factor]) / 1000) // then in any cell: =TONNES([@kWh], [@Fuel])
Good for a calculation that appears in ten places and must stay consistent. The downside: colleagues can't see the logic in the cell, so document the name's Comment field in Name Manager.
BYROW · BYCOL · MAP · SCAN 365 Advanced
Apply a LAMBDA to each row, column, or element of an array and get an array back. =BYROW(B2:E9, LAMBDA(r, SUM(r))) gives per-row totals as one spilled formula; =SCAN(0, C2:C9, LAMBDA(acc, x, acc + x)) is a running total. Rarely essential — but they remove the need for helper columns.
Errors & debugging
Every error value is Excel telling you something specific. Learn the ten messages and most "my formula is broken" moments take a minute rather than an afternoon.
Every error explained
| Error | Excel is saying | Usual cause | Fix |
|---|---|---|---|
| #N/A | "Not available" — a lookup found nothing | Value not in the list; trailing space; number vs text mismatch; VLOOKUP looking in the wrong column | Check the key with =F2=A2 and LEN; TRIM the data; wrap with IFNA for a friendly message |
| #REF! | A reference no longer exists | You deleted a row/column/sheet a formula pointed at; VLOOKUP's column number is bigger than the table; INDEX row out of range | Undo if you can; otherwise re-point the reference. Tables and XLOOKUP avoid the column-number case |
| #VALUE! | Wrong type of data | Maths on text ("abc"*2, or a "number" stored as text); a date typed as text; a function given a range where it wants one value | Find the text cell (ISTEXT, or Ctrl+` to show formulas); convert with VALUE or Text to Columns |
| #DIV/0! | Division by zero (or by blank) | Denominator is 0 or empty — typical in averages of empty periods | =IF(D2=0, "", C2/D2) or IFERROR(C2/D2, "") |
| #NAME? | Excel doesn't recognise a name | Misspelt function (SUMM); text without quotes; a defined name that doesn't exist; a 365-only function on older Excel | Check spelling; add quotes; open Name Manager; check Excel version |
| #NUM! | Invalid number | Square root of a negative; date before 1900; result too large; IRR that can't converge | Check inputs; use ABS where appropriate |
| #NULL! | Two ranges don't intersect | A space between references where a comma or colon was meant: SUM(A1 B1) | Replace the space with , or : |
| #SPILL! | A dynamic-array result has nowhere to go | Cells below/right are not empty; the formula is inside a Table; merged cells in the way | Clear the area or move the formula; keep spills outside Tables |
| #CALC! | The calculation engine can't produce a result | FILTER returned nothing and no if_empty was given; a LAMBDA with the wrong number of inputs; an array inside an array | Add the if_empty argument; check parameters |
| #GETTING_DATA | Still fetching | Data types / external queries still loading | Wait; refresh if it persists |
| ######## | Not an error — the column is too narrow | Number or date wider than the cell; or a negative date/time | Double-click the column border to autofit; check for negative dates |
| Circular reference | A formula refers to its own cell (directly or via others) | =SUM(A1:A10) placed in A10; or a chain of cells that loops | Status bar shows the cell address; Formulas → Error Checking → Circular References lists them |
That's a warning, not an error. Hover the cell and click the ⚠ to see which: "Number stored as text", "Formula omits adjacent cells", "Inconsistent formula" (this cell differs from its neighbours — often the real bug). Convert or fix; use "Ignore Error" only when it really is intended.
Evaluate Formula — Excel's own step-by-step debugger Basic
The single most useful button for understanding a formula someone else wrote. Select the cell, then Formulas tab → Formula Auditing group → Evaluate Formula. A dialog opens showing the formula with one part underlined: that's the piece Excel will work out next. Press Evaluate and the underlined piece is replaced, in place, by its result. Keep pressing and you watch the formula collapse to its answer — exactly the inside-out reading from chapter 2, done for you.
| Button | What it does | Use it when |
|---|---|---|
| Evaluate | Works out the underlined part and replaces it with its value | Every step — this is the one you'll press most |
| Step In | Opens the formula of the referenced cell in a sub-panel, so you can evaluate that formula too | The underlined part is a reference to another cell and you suspect the problem is there |
| Step Out | Returns from that sub-panel with the referenced cell's value filled in | You've seen what the other cell produces |
| Restart | Starts again from the original formula (the button becomes Restart once you reach the end) | You clicked past the interesting bit |
Here's the same process on a realistic formula. Cells C2:C5 hold 48,200 / 96,500 / 31,500 / 210,000 kWh and F1 holds the factor 0.18293. Step through it exactly as the dialog would:
- Watch which branch runs. An
IFonly evaluates the side that wins. If the answer looks wrong, check the test resolved the way you assumed — a broken formula in the branch that never runs stays invisible. - Italic text means "already calculated". Excel shows a value in italics when it came from a cell it has evaluated on a previous pass; it won't recalculate it.
- It cannot step into everything. Step In is greyed out for references to another workbook, to a cell that contains a constant rather than a formula, and for the same reference a second time.
- Nothing is changed. Unlike F9, Evaluate Formula never edits the cell — you can close it at any point with no risk.
You want the whole story in order, you're reading someone else's work, an IF is picking the wrong branch, or the formula is long enough that you'd lose your place doing it by hand. Safe — it can't alter the cell.
You want one specific piece checked immediately: select just that part in the formula bar and press F9. Faster, but it writes the result into the formula — always press Esc afterwards, never Enter.
Debugging toolkit
- Read the error. The table above tells you which family of problem you have — don't start by rewriting the formula.
- Evaluate a piece. In the formula bar, select one function or bracket group and press F9. Look at the value. Esc to leave the formula unchanged. For the whole formula in order, use Evaluate Formula.
- Check the inputs, not the formula. Most lookup failures are data: a trailing space, "1001" as text vs 1001 as a number, a date with a hidden time.
=LEN(A2),=ISNUMBER(A2),=A2=F5answer these in seconds. - Trace it. Formulas → Trace Precedents draws arrows from every input into this cell; Trace Dependents shows what breaks if you change it. Clear with Remove Arrows.
- Show formulas. Ctrl+` reveals a hard-coded number hiding in a column of formulas, or a formula that was pasted as a value.
- Find inconsistencies. Ctrl+G → Special → Row differences selects the cells whose formula differs from the first cell in each row.
- Isolate. Copy the formula to a scratch cell and remove one layer at a time (IFERROR first — it's hiding the real error).
- Check calculation mode. If nothing updates, Formulas → Calculation Options may be on Manual (someone set it to speed up a big file). F9 recalculates.
FORMULATEXT · ISFORMULA
FORMULATEXT(reference) · ISFORMULA(reference)
Show a neighbouring cell's formula as text (useful in documentation columns or for auditing a model), or test whether a cell contains a formula at all — combined with Conditional Formatting, =NOT(ISFORMULA(A1)) highlights every hard-coded value in a calculation block.
Silent errors — the dangerous kind
Formulas that don't show an error but give a wrong number are the ones that reach a client. The usual suspects:
- VLOOKUP without
FALSEreturning a near-miss. - A SUM range that stops one row short of the data (check with the status-bar sum after selecting the whole column).
- IFERROR(…, 0) hiding a broken reference.
- SUMIFS criteria ranges of different sizes (they must all be the same shape).
- A pasted value sitting in a column of formulas (Ctrl+` finds it).
- Calculation set to Manual.
- Percentages entered as 5 instead of 0.05 (or vice versa).
Quick reference
The pages to keep open: shortcuts, a lookup decision flow, and an alphabetical index of every function in this handbook.
Keyboard shortcuts worth learning
Editing & formulas
Navigation & selection
Data & formatting
Which lookup should I use?
Function A–Z
Every function with its own entry in this handbook. Click to jump.