ReDim Preserve Uninitialized Array
Why ReDim Preserve crashes on an uninitialized VBA array, the canonical (Not arr) = -1 guard, and the Python list pattern that never has the problem.
What goes wrong
ReDim Preserve resizes a dynamic array while keeping its current contents. The keyword Preserve tells VBA to copy the existing buffer into the new one. If there is no existing buffer — because the array was declared but never ReDim'd — VBA throws Subscript out of range (Run-time error 9) or, worse, silently corrupts state in some Office hosts.
VBA — crashes
Dim items() As String
' (never ReDim'd)
For i = 1 To 5
ReDim Preserve items(1 To i)
items(i) = "row " & i
' First iteration: Subscript
' out of range — no buffer
' to preserve
Next iWhy it crashes
' Step-through: ' i = 1 ' ReDim Preserve items(1 To 1) ' → "preserve from where?" ' → Run-time error 9 ' ' Plain ReDim (no Preserve) ' would have worked here.
The canonical fix: (Not arr) = -1
The reliable, undocumented way to detect an uninitialized dynamic array in VBA: apply the Not operator to it and compare with -1. If it matches, the array has never been ReDim'd and you must use plain ReDim first.
VBA — fixed
Dim items() As String
For i = 1 To 5
If (Not items) = -1 Then
ReDim items(1 To 1)
Else
ReDim Preserve items(1 To i)
End If
items(i) = "row " & i
Next iVBA — cleaner
Dim items() As String ReDim items(1 To 0) ' Some hosts allow zero-length; ' then Preserve grows from there. ' Or: just always plain ReDim ' on the first pass, Preserve ' on subsequent passes.
The (Not arr) = -1 idiom works because VBA stores a descriptor for uninitialized arrays whose bitwise NOT happens to evaluate to -1. It's undocumented but stable across all Office versions since 2003.
Why the whole pattern is brittle
ReDim Preserve in a loop is O(n²) — every iteration copies the entire current buffer into a new one. For a 10,000-row collection that's ~50 million copy operations. VBA tolerates it; performant code shouldn't.
Even in pure VBA, the idiomatic fix is to size the array once (or use a Collection) and skip the per-iteration Preserve entirely. In Python, list.append()is amortized O(1) and the problem disappears.
The Python equivalent: list.append()
Python lists have no separate uninitialized state. items = []is a fully-formed empty list — you can append immediately, no guard needed.
VBA (with guard)
Dim items() As String
For i = 1 To 5
If (Not items) = -1 Then
ReDim items(1 To 1)
Else
ReDim Preserve items(1 To i)
End If
items(i) = "row " & i
Next iPython (no guard)
items = []
for i in range(1, 6):
items.append(f"row {i}")
# That's it. No guard.
# No O(n²) copying.
# No "uninitialized array" state.Where the VBA pattern needs ~9 lines of error-prone bookkeeping, the Python equivalent is 3 lines that can't fail.
How the converter handles it
VBAtoPython recognizes the ReDim Preserve arr(1 To n) + index assignment pattern inside loops and rewrites it to arr.append(value). The (Not arr) = -1 guard becomes implicit because Python doesn't need it.
Edge case: if your VBA accesses UBound(arr) as part of the preserve logic, the converter flags it for manual review — Python's len(arr) is the equivalent but the index math may differ depending on the lower bound you originally chose.
Related
- VBA ReDim 0 to -1 Error — The sibling error when bounds collapse to 0 to -1.
- VBA Arrays & ReDim to Python Lists — The full umbrella guide for arrays.
- VBA Error Handling to Python — Translating On Error patterns to try/except.
Convert your ReDim Preserve loops
The free converter rewrites ReDim Preserve patterns to list.append() automatically — no guards, no O(n²) copies.