· Validation & Checksums · 16 min read

IFERROR Mask Detector: Find the Errors Your Workpaper Is Hiding From You

Finds every IFERROR and IFNA formula on every sheet, classifies them as safe or suspicious, and flags the ones that are silently hiding #REF! errors behind a blank cell. Your workpaper looks clean — the macro proves otherwise.

Share:

TL;DR: IFERROR is the most dangerous function in Excel. It hides #REF!, #N/A, #VALUE!, and #DIV/0! behind whatever fallback you give it — and if that fallback is a blank string, you’ll never know the error exists. This macro finds every IFERROR and IFNA in your workbook, categorizes each one, and creates an audit report that flags the ones most likely masking real problems. The original data stays untouched.

The Problem

The Henderson state apportionment workpaper passes the eye test. Every cell has a value — nothing showing #REF! or #N/A. The partner signs off. Three months later, during the multi-state extension calculation, you discover that cell D14 on Sched-A has been returning zero the whole time. Not because the California sales factor is actually zero, but because the IFERROR on that cell is hiding a #REF! from a deleted prior-year sheet. The formula reads =IFERROR('2024-Henderson.xlsx'!CA_Sales, 0), and since the file was moved to the archive folder, Excel can’t find it.

IFERROR doesn’t fix errors. It silences them. And in a 30-tab workpaper built over three tax seasons by four different preparers, it’s hiding errors you don’t even know exist. This macro shows you every single one — and tells you which are probably fine and which are probably not.

#Prerequisites & Setup

What you’ll need:

  • Excel 2016+ (desktop)
  • A workbook with formulas — the macro scans every sheet
  • No special setup required — just run it

Limitations:

  • Does not detect errors inside user-defined functions (UDFs) — only standard IFERROR/IFNA formulas
  • Classification is heuristic, not guaranteed — “suspicious” means “worth checking,” not “definitely broken”
  • The macro reads from formulas but does not modify the original sheets — all output goes to a new “IFERROR-Audit” report sheet
  • Formulas on protected sheets are still scanned (the macro reads only, no writes to original sheets)

#The Macro

Option Explicit

Sub AuditIFERROR()
    ' ── IFERROR Mask Detector ──────────────────────────
    ' Scans every sheet for IFERROR() and IFNA()
    ' formulas. Classifies each as:
    '   GENUINE  — Reasonable fallback (value, text,
    '              reference, or named range)
    '   SUSPICIOUS — Fallback is blank, empty string,
    '                or zero — may be masking errors
    '   REDUNDANT — Wraps a function that can't error
    '               (e.g., simple arithmetic)
    ' Creates an "IFERROR-Audit" report sheet with
    ' hyperlinks back to each source cell. Original
    ' data is never modified.
    ' ────────────────────────────────────────────────────

    ' ── Configuration ──────────────────────────────────
    Const OUT_SHEET As String = "IFERROR-Audit"
    Const BLANK_THRESHOLD As String = ""

    ' ── State management ───────────────────────────────
    Application.ScreenUpdating = False
    Application.Calculation = xlCalculationManual

    ' ── Variables ──────────────────────────────────────
    Dim ws As Worksheet, rpt As Worksheet
    Dim cell As Range, formulaCells As Range
    Dim outRow As Long
    Dim fText As String, fUpper As String
    Dim funcName As String, fallback As String
    Dim clas As String, note As String
    Dim genuine As Long, suspicious As Long, redundantCount As Long
    Dim sheetCount As Long

    ' ── Error handling ─────────────────────────────────
    On Error GoTo CleanUp

    ' ── Step 1: Delete old report if present ───────────
    On Error Resume Next
    Application.DisplayAlerts = False
    ThisWorkbook.Worksheets(OUT_SHEET).Delete
    Application.DisplayAlerts = True
    On Error GoTo CleanUp

    ' ── Step 2: Create report sheet ────────────────────
    Set rpt = ThisWorkbook.Worksheets.Add( _
        After:=ThisWorkbook.Worksheets(ThisWorkbook.Worksheets.Count))
    rpt.Name = OUT_SHEET

    ' ── Step 2a: Write headers ─────────────────────────
    rpt.Range("A1:F1").Value = Array( _
        "Cell", "Sheet", "Function", "Fallback Value", _
        "Classification", "Formula Text")
    rpt.Range("A1:F1").Font.Bold = True
    rpt.Range("A1:F1").Interior.Color = RGB(50, 50, 50)
    rpt.Range("A1:F1").Font.Color = vbWhite

    outRow = 2
    genuine = 0: suspicious = 0: redundantCount = 0
    sheetCount = 0

    ' ── Step 3: Scan every sheet ───────────────────────
    For Each ws In ThisWorkbook.Worksheets
        If ws.Name = OUT_SHEET Then GoTo NextSheet

        ' Find all formula cells on this sheet
        On Error Resume Next
        Set formulaCells = ws.Cells.SpecialCells(xlCellTypeFormulas)
        On Error GoTo CleanUp

        If formulaCells Is Nothing Then GoTo NextSheet

        Dim foundOnSheet As Boolean
        foundOnSheet = False

        For Each cell In formulaCells
            fText = cell.Formula
            fUpper = UCase(fText)

            ' ── Check for IFERROR ───────────────────────
            If InStr(fUpper, "IFERROR(") > 0 Then
                funcName = "IFERROR"
            ElseIf InStr(fUpper, "IFNA(") > 0 Then
                funcName = "IFNA"
            Else
                GoTo NextCell
            End If

            foundOnSheet = True

            ' ── Extract fallback value ──────────────────
            fallback = ExtractFallback(fText, funcName)

            ' ── Classify ────────────────────────────────
            call ClassifyIFError fText, funcName, fallback, _
                                clas, note

            ' ── Write to report ─────────────────────────
            rpt.Hyperlinks.Add _
                Anchor:=rpt.Cells(outRow, 1), _
                Address:="", _
                SubAddress:="'" & ws.Name & "'!" & cell.Address(False, False), _
                TextToDisplay:=cell.Address(False, False)
            rpt.Cells(outRow, 2).Value = ws.Name
            rpt.Cells(outRow, 3).Value = funcName
            rpt.Cells(outRow, 4).Value = fallback
            rpt.Cells(outRow, 5).Value = clas & " — " & note
            rpt.Cells(outRow, 6).Value = "'" & fText

            ' ── Color-code classification ───────────────
            Select Case clas
                Case "SUSPICIOUS"
                    rpt.Cells(outRow, 5).Interior.Color = RGB(254, 202, 202)
                    rpt.Cells(outRow, 5).Font.Bold = True
                    suspicious = suspicious + 1
                Case "GENUINE"
                    rpt.Cells(outRow, 5).Interior.Color = RGB(217, 249, 212)
                    genuine = genuine + 1
                Case "REDUNDANT"
                    rpt.Cells(outRow, 5).Interior.Color = RGB(254, 240, 138)
                    redundantCount = redundantCount + 1
            End Select

            outRow = outRow + 1

NextCell:
        Next cell

        If foundOnSheet Then sheetCount = sheetCount + 1

NextSheet:
    Next ws

    ' ── Step 4: Format report ──────────────────────────
    If outRow > 2 Then
        With rpt
            .Columns("A:F").AutoFit
            .Columns("F").ColumnWidth = 65
            .Range("A1").Select
            ActiveWindow.FreezePanes = True
        End With

        ' Sort: SUSPICIOUS first, then REDUNDANT, then GENUINE
        rpt.Sort.SortFields.Clear
        rpt.Sort.SortFields.Add _
            Key:=rpt.Range("E2:E" & outRow - 1), _
            SortOn:=xlSortOnValues, Order:=xlAscending, _
            CustomOrder:="SUSPICIOUS,REDUNDANT,GENUINE"
        With rpt.Sort
            .SetRange rpt.Range("A1:F" & outRow - 1)
            .Header = xlYes
            .Apply
        End With
    End If

    ' ── Step 5: Summary MsgBox ─────────────────────────
    Dim total As Long, msg As String
    total = genuine + suspicious + redundantCount

    If total = 0 Then
        msg = "No IFERROR or IFNA formulas found in this workbook."
    Else
        msg = "Audit complete — " & total & " IFERROR/IFNA " & _
              "formula(s) found across " & sheetCount & " sheet(s)." & _
              vbCrLf & vbCrLf & _
              "  SUSPICIOUS (red)  : " & suspicious & _
              " — likely masking errors" & vbCrLf & _
              "  REDUNDANT (yellow): " & redundantCount & _
              " — wraps safe formula" & vbCrLf & _
              "  GENUINE (green)   : " & genuine & _
              " — reasonable fallback" & vbCrLf & vbCrLf & _
              "See '" & OUT_SHEET & "' sheet. Start with " & _
              "SUSPICIOUS (red) items first."
    End If

    MsgBox msg, vbInformation, "IFERROR Audit Report"

CleanUp:
    Application.ScreenUpdating = True
    Application.Calculation = xlCalculationAutomatic

    If Err.Number <> 0 Then
        MsgBox "Error " & Err.Number & ": " & Err.Description, _
               vbCritical, "Macro Error"
    End If
End Sub

' ── Helper: Extract the second argument (fallback) ─────
Private Function ExtractFallback(formula As String, _
                                  fname As String) As String
    ' Finds IFERROR(expr, fallback) and returns fallback
    ' Uses simple comma counting — works for nested parens
    ' but not for deeply weird edge cases
    Dim pos As Long, depth As Long, i As Long
    Dim startArg As Long

    pos = InStr(UCase(formula), fname & "(")
    If pos = 0 Then
        ExtractFallback = "[parse error]"
        Exit Function
    End If

    ' Walk past the first argument
    depth = 0
    startArg = 0
    For i = pos + Len(fname) + 1 To Len(formula)
        Select Case Mid(formula, i, 1)
            Case "("
                depth = depth + 1
            Case ")"
                If depth = 0 Then
                    ' End of IFERROR — fallback is empty
                    ExtractFallback = "[empty]"
                    Exit Function
                End If
                depth = depth - 1
            Case ","
                If depth = 0 Then
                    startArg = i + 1
                    Exit For
                End If
        End Select
    Next i

    If startArg = 0 Then
        ExtractFallback = "[no fallback]"
        Exit Function
    End If

    ' Extract from startArg to closing paren (matching depth)
    depth = 0
    For i = startArg To Len(formula)
        Select Case Mid(formula, i, 1)
            Case "("
                depth = depth + 1
            Case ")"
                If depth = 0 Then
                    ExtractFallback = Trim(Mid(formula, startArg, _
                                               i - startArg))
                    Exit Function
                End If
                depth = depth - 1
        End Select
    Next i

    ExtractFallback = Trim(Mid(formula, startArg))
End Function

' ── Helper: Classify the IFERROR/IFNA ─────────────────
Private Sub ClassifyIFError(ByVal formula As String, _
                             ByVal fname As String, _
                             ByVal fallback As String, _
                             ByRef clas As String, _
                             ByRef note As String)
    Dim innerExpr As String, innerUpper As String

    ' ── Step 1: Check fallback for suspicious patterns ──
    If fallback = """""" Or fallback = "" Or _
       fallback = "[empty]" Then
        clas = "SUSPICIOUS"
        note = "Fallback is blank — errors are invisible"
        Exit Sub
    End If

    If fallback = "0" Then
        clas = "SUSPICIOUS"
        note = "Fallback is zero — may hide missing values"
        Exit Sub
    End If

    If fallback = """" & "-" & """" Or _
       fallback = """-""" Or _
       fallback = """N/A""" Or _
       fallback = """n/a""" Then
        clas = "SUSPICIOUS"
        note = "Placeholder fallback — check if errors exist"
        Exit Sub
    End If

    ' ── Step 2: Check if the wrapped expression is safe ──
    ' Extract the inner expression (first argument)
    innerExpr = ExtractInner(formula, fname)
    innerUpper = UCase(innerExpr)

    ' If the inner expression is just arithmetic, it can't
    ' produce a formula error (divide by zero is about
    ' values, not formulas — #DIV/0! is a value error)
    If IsSafeExpression(innerExpr, innerUpper) Then
        clas = "REDUNDANT"
        note = "Wraps safe expression — IFERROR unnecessary"
        Exit Sub
    End If

    ' ── Step 3: Check inner for likely error sources ────
    If InStr(innerUpper, "VLOOKUP") > 0 Or _
       InStr(innerUpper, "XLOOKUP") > 0 Or _
       InStr(innerUpper, "MATCH") > 0 Or _
       InStr(innerUpper, "INDEX") > 0 Then
        clas = "GENUINE"
        note = "Wraps lookup — likely handles #N/A"
        Exit Sub
    End If

    If InStr(innerUpper, "!") > 0 Then
        clas = "GENUINE"
        note = "Cross-sheet reference — could break if sheet deleted"
        Exit Sub
    End If

    If InStr(innerUpper, "[") > 0 Then
        clas = "SUSPICIOUS"
        note = "External workbook reference — common #REF! source"
        Exit Sub
    End If

    If InStr(innerUpper, "/") > 0 Then
        clas = "GENUINE"
        note = "Contains division — handles #DIV/0!"
        Exit Sub
    End If

    ' Default: unclear intent
    clas = "GENUINE"
    note = "Unclear risk — review manually"
End Sub

' ── Helper: Extract first argument from IFERROR/IFNA ──
Private Function ExtractInner(formula As String, _
                               fname As String) As String
    Dim pos As Long, depth As Long, i As Long
    Dim startExpr As Long

    pos = InStr(UCase(formula), fname & "(")
    If pos = 0 Then
        ExtractInner = ""
        Exit Function
    End If

    startExpr = pos + Len(fname) + 1
    depth = 0

    For i = startExpr To Len(formula)
        Select Case Mid(formula, i, 1)
            Case "("
                depth = depth + 1
            Case ")"
                If depth = 0 Then
                    ExtractInner = Trim(Mid(formula, startExpr, _
                                            i - startExpr))
                    Exit Function
                End If
                depth = depth - 1
            Case ","
                If depth = 0 Then
                    ExtractInner = Trim(Mid(formula, startExpr, _
                                            i - startExpr))
                    Exit Function
                End If
        End Select
    Next i

    ExtractInner = Trim(Mid(formula, startExpr))
End Function

' ── Helper: Check if expression is inherently safe ─────
Private Function IsSafeExpression(formula As String, _
                                   fUpper As String) As Boolean
    ' An expression is "safe" if it can't produce formula
    ' errors — only simple arithmetic and constants.
    ' Lookups, external refs, INDIRECT, OFFSET, etc. are
    ' NOT safe.

    Dim unsafePatterns As Variant
    Dim p As Variant

    unsafePatterns = Array( _
        "VLOOKUP", "XLOOKUP", "HLOOKUP", _
        "MATCH", "INDEX", "INDIRECT", "OFFSET", _
        "SUMIF", "COUNTIF", "AVERAGEIF", _
        "SUMIFS", "COUNTIFS", "AVERAGEIFS", _
        "!", "[", "INDIRECT", "ADDRESS", _
        "CELL(", "INFO(", "GETPIVOTDATA", _
        "DSUM", "DGET", "DCOUNT")

    For Each p In unsafePatterns
        If InStr(fUpper, CStr(p)) > 0 Then
            IsSafeExpression = False
            Exit Function
        End If
    Next p

    ' If we reach here, the expression looks like simple
    ' arithmetic or constants — safe from formula errors
    IsSafeExpression = True
End Function

#How It Works

#Three-tier classification: not all IFERRORs are dangerous

The macro doesn’t just find IFERRORs — it rates them. Every IFERROR/IFNA formula gets one of three classifications:

ClassificationColorWhat it means
SUSPICIOUSRedFallback is blank, zero, or a placeholder. Errors are invisible. Investigate immediately.
REDUNDANTYellowWraps a simple arithmetic expression that can’t produce formula errors. The IFERROR is dead weight — it adds confusion without adding protection.
GENUINEGreenReasonable fallback for a formula that can legitimately error — lookup #N/A, division by zero, cross-sheet reference. Probably fine, but review to confirm.

The report sheet is sorted with SUSPICIOUS items at the top. You open the report, see 3 red rows, and investigate those before anything else. If there are 40 green rows below them, they’re vetted and can be skimmed.

#Suspicious: when IFERROR hides the truth

If fallback = """""" Or fallback = "" Or _
   fallback = "[empty]" Then
    clas = "SUSPICIOUS"
    note = "Fallback is blank — errors are invisible"

The blank-string fallback is the most common and most dangerous pattern. =IFERROR('2024-Henderson.xlsx'!CA_Sales, "") — the cell looks empty, the column still sums, and you have no way to know the data source disappeared. Every preparer has done this, usually because they inherited the formula and never questioned it.

Zero is the second-most-dangerous. =IFERROR(VLOOKUP(A2, RateTable, 2, 0), 0) — if the lookup fails, the cell quietly returns zero. The tax due for that jurisdiction drops by $48,000 and nobody notices because the cell isn’t blank and doesn’t trigger any formula error.

Placeholder text like "-" or "N/A" gets flagged as suspicious too — even though it’s visible, it’s masking the type of error. A cell showing "-" could be hiding a #N/A (harmless, expected) or a #REF! (data is gone). The preparer can’t tell the difference without investigating the underlying formula.

#Redundant: IFERROR wrapping safe arithmetic

If IsSafeExpression(innerExpr, innerUpper) Then
    clas = "REDUNDANT"
    note = "Wraps safe expression — IFERROR unnecessary"

The IsSafeExpression helper checks the inner expression for anything that could produce a formula error: lookups, cross-sheet references, external workbook references, INDIRECT, OFFSET, and a dozen other functions. If none of these are present, the expression is simple arithmetic — and an IFERROR around it is redundant.

=IFERROR(A2+B2-C2, 0) — this can never produce a #REF! or #N/A. The only possible error is #VALUE! if someone types text into an input cell, and you probably want that error to surface. Redundant IFERRORs are clutter — they don’t hurt the model, but they mask the preparer’s intent and make auditing harder.

#Genuine: reasonable error handling

If InStr(innerUpper, "VLOOKUP") > 0 Or _
   InStr(innerUpper, "XLOOKUP") > 0 Then
    clas = "GENUINE"
    note = "Wraps lookup — likely handles #N/A"

Lookups are the most common legitimate IFERROR use case. Account 11010 might not exist in the rate table. The schedule header might not match any row in the TB. These are expected-not-found scenarios, and IFERROR handles them cleanly.

Division gets the GENUINE tag too. =B14/C14 with IFERROR protects against a #DIV/0! when the denominator is zero — common in ratio analysis and apportionment percentage calculations.

Cross-sheet references get a GENUINE classification because they’re common and intentional — but the macro notes “could break if sheet deleted” in the report. The preparer can see the risk and decide whether to harden the reference.

#The external workbook trap

If InStr(innerUpper, "[") > 0 Then
    clas = "SUSPICIOUS"
    note = "External workbook reference — common #REF! source"

External workbook references deserve special attention. When the source file moves to an archive folder, gets renamed, or is on a network share that changes, every IFERROR that references it silently stops working. The output looks fine — zero, blank, whatever the fallback is — but the underlying data is gone. This is the exact scenario from the problem statement. The macro flags every external-reference IFERROR as SUSPICIOUS regardless of what the fallback is, because the risk of a broken link is higher than for any other formula type.

#Fallback extraction: parsing nested IFERRORs

Private Function ExtractFallback(formula As String, _
                                  fname As String) As String
    ' Walk past the first argument with paren counting

The ExtractFallback helper parses the IFERROR or IFNA formula by counting parenthesis depth. It walks past the first argument (which may contain its own commas and nested parens) and captures the second argument. This handles =IFERROR(VLOOKUP(A2,IF(B2>0,Data,Other),2,0), "N/A") correctly — the comma inside the nested IF doesn’t confuse it.

The companion ExtractInner function does the same for the first argument, which is needed by the IsSafeExpression check to determine whether the wrapped expression needs error handling at all.

#Read-only: the original workpaper is never modified

The macro uses SpecialCells(xlCellTypeFormulas) to read formula text, and writes all findings to a new IFERROR-Audit sheet. The original workpaper sheets are never modified — not even a cell value read-and-rewrite. If the preparer disagrees with the classification, or the report is too noisy, they delete the IFERROR-Audit tab and the workpaper is exactly as it was before.

This is especially important for an audit macro. If the macro accidentally modified cells while “auditing” them, nobody would trust it. Read-only output means zero risk.

#Why 3 classification tiers instead of 2

Three tiers create action priority. A 2-tier system (suspicious vs. fine) would dump everything with a fallback value into “fine” — but IFERROR wrapping a cross-sheet VLOOKUP and IFERROR wrapping =A2+B2 are not the same thing. The redundant tier catches wasteful IFERRORs that add confusion, even though they don’t mask real errors. The preparer can see them and decide to simplify the formula for the next reviewer.

#Adapt It

Get the next macro in your inbox

One copy-paste-ready macro recipe every two weeks. No spam, no VBA theory — just automation that saves you time.

One macro recipe every two weeks. Unsubscribe anytime.

E

Excel Macro Guy

Excel enthusiast · married to an accountant

I love Excel. My wife is an accountant. Every busy season, I watch her wrestle with workpapers and think "a macro could do that in half a second." So I build them. She tests them on real client data. What survives gets published here.

More about me →