Error handling and the classic pitfalls
On Error in place of try/catch, a cleanup pattern that always restores state, and the mistakes that bite every VBA author.
On Error instead of try/catch
VBA has no exceptions and no finally. Error handling is a jump to a label with a single global error object, Err, which holds Number, Description and Source until you clear it or leave the procedure.
Public Function ReadNumber(ByVal text As String) As Double
On Error GoTo Bad
ReadNumber = CDbl(text) ' raises error 13 on "abc"
Exit Function ' required: otherwise the handler runs on success
Bad:
Select Case Err.Number
Case 13 ' Type mismatch
ReadNumber = 0
Case Else
ReadNumber = 0
Debug.Print "unexpected: " & Err.Number & " - " & Err.Description
End Select
End Function
Public Sub WrapUp()
On Error GoTo Cleanup ' VBA's stand-in for finally
Dim f As Long
f = FreeFile
Open "C:\temp\input.txt" For Input As #f
Dim line As String
Do While Not EOF(f)
Line Input #f, line
' ... use line ...
Loop
Close #f
Exit Sub
Cleanup:
On Error Resume Next ' closing may itself fail; do not loop
Close #f
On Error GoTo 0
If Err.Number <> 0 Then
MsgBox "Failed: " & Err.Number & " - " & Err.Description, vbCritical
Err.Clear
End If
End SubOn Error GoTo 0disables the current handler;On Error Resume Nextignores errors until you turn it off, which is how bugs hide.- An
Exit SuborExit Functionplaced just before the handler label is what stops the handler running when nothing failed. - Errors do not unwind the call stack: a handler in a caller sees the same error object, but a handler never set in a callee leaves the error unhandled and shows the standard dialog.
Err.Raise 5, "MyModule", "custom message"raises your own error, andErr.Clearresets the object to zero.
The pitfalls that actually bite
| Symptom | Cause | Fix |
|---|---|---|
| Run-time error 6, Overflow | Dim i As Integer counting past 32,767 rows | Declare all counters and row indexes as Long |
| Numbers become text | + used on mixed strings and numbers | Use & to join text and let Option Explicit keep types honest |
| Wrong sheet gets modified | Unqualified Range or ActiveSheet | Hold a Dim ws As Worksheet and qualify every call |
| Macro seems to do nothing | ScreenUpdating left False, or events left off | Restore both in a cleanup label on every path |
| Handler fires repeatedly | Writing inside Worksheet_Change re-triggers it | EnableEvents = False around the write, restored after |
| Dates change meaning | String conversion depends on the machine locale | Use DateSerial and real Date values, never text |
| Comparison ignores case rules | Option Compare default is binary | Set Option Compare Text explicitly, and state it at the top of the module |
| Loop takes minutes | One COM call per cell | Read the block into a Variant array and write it back once |
| Floating point test fails | 0.1 + 0.2 = 0.3 is False | Compare with a tolerance, or use Currency for money |
| Array indexes are off by one | Split and Array() are 0-based while ranges are 1-based | Use LBound and UBound instead of assuming |
' Two conversions that look equivalent and are not
Debug.Print CInt(2.5), CLng(2.5) ' 2 2 banker's rounding, not half-up
Debug.Print Application.Round(2.5, 0) ' 2
Debug.Print Int(-2.5), Fix(-2.5) ' -3 -2 Int floors, Fix truncates
' Safe comparison for floating point
Public Function NearlyEqual(ByVal a As Double, ByVal b As Double, _
Optional ByVal tol As Double = 0.0000001) As Boolean
NearlyEqual = Abs(a - b) < tol
End Function
' Variant results need explicit handling: IsError tests a cell error value
Public Sub ShowErrors()
Dim v As Variant
v = Application.VLookup("missing", Range("A1:B10"), 2, False)
If IsError(v) Then
Debug.Print "not found: " & CStr(CVErr(v))
Else
Debug.Print v
End If
End Sub⚠️
Running a macro clears Excel's undo stack, so there is no Ctrl + Z after a destructive write, and a run-time error can leave the workbook half-modified. Take a copy of the sheet, or write to a separate output sheet, before anything that deletes or overwrites in bulk.
Making it testable
Option Explicit
' Keep the logic free of Excel objects so it can be called from a test harness.
Public Function NetTotal(ByVal amounts As Variant, _
ByVal refunds As Variant) As Double
Dim total As Double, i As Long
For i = LBound(amounts) To UBound(amounts)
If IsNumeric(amounts(i)) Then total = total + CDbl(amounts(i))
Next i
For i = LBound(refunds) To UBound(refunds)
If IsNumeric(refunds(i)) Then total = total - CDbl(refunds(i))
Next i
NetTotal = total
End Function
Public Sub TestNetTotal()
Dim got As Double
got = NetTotal(Array(10, 20, 30), Array(5))
If got <> 55 Then
Debug.Print "FAIL: expected 55, got " & got
Else
Debug.Print "PASS"
End If
End Sub
' Wrap anything that touches the UI so the pure parts stay testable
Public Sub ImportAll()
On Error GoTo Cleanup
Application.ScreenUpdating = False
Application.EnableEvents = False
Dim rows As Variant
rows = ThisWorkbook.Worksheets("Input").Range("A2:C100").Value2
' ... validate, then write ...
Debug.Print NetTotal(Array(1, 2), Array(0))
Exit Sub
Cleanup:
Application.ScreenUpdating = True
Application.EnableEvents = True
If Err.Number <> 0 Then Debug.Print Err.Number & ": " & Err.Description
End Sub- Pass arrays into functions and return values: a procedure that reads cells directly can only be tested by opening the workbook and running it.
- Assertions via
Debug.Print "FAIL: ..."are crude but work, and they cost nothing in a production file once the test procedures are not called. - Keep a known-good copy of the workbook and a version history: VBA has no diff, so comparing two modules by eye is the only review you get.
- Delete or password-protect the VBA project before distribution if the workbook is meant to be used rather than read.
FAQ
Can I break on an error instead of jumping to a handler?
Yes. In the VBE, choose Tools > Options > General, and set Error Trapping to Break on All Errors. The debugger stops at the failing line with the current values, which is far more useful than a message box.
Why does On Error Resume Next make things worse?
It hides the error but lets execution continue on invalid state, so a later line fails in a confusing place. Use it only around a single statement you expect to fail, then clear it with
On Error GoTo 0 and check Err.Number deliberately.Related
Macros, worksheets and ranges The VBA editor and the language
Last refreshed 2026-09-18.