Every time VBA reads or writes a cell individually in a loop, it crosses the boundary between VBA and the Excel object model. Each crossing has overhead. Across 10,000 cells, that overhead dominates execution time. Arrays eliminate this entirely. Read the whole range into an array in one call, process every value at VBA speed in memory, then write results back in one call. The same 10,000-row operation typically runs 50–100× faster. This guide covers array declaration, all three array types, reading and writing ranges, multi-dimensional arrays, and six complete practical examples.
The Three Array Types in VBA
VBA supports three array types distinguished by when and how their size is determined. Choosing the wrong type is the most common source of array-related runtime errors — specifically the "subscript out of range" error that occurs when you access an index beyond the array boundary.
Reading a Range into an Array
Assigning a Range's Value property to a Variant variable is the fastest way to read data from a worksheet. The result is always a two-dimensional array regardless of the range shape — even a single column produces a 2D array where the second dimension is always 1. Understanding this prevents the most common beginner error: treating a single-column range read as a 1D array.
Example 1: Bulk Calculation — 50× Faster Than Cell-by-Cell
A macro needs to calculate a gross margin percentage for 20,000 rows and write results to column E. The cell-by-cell version takes 8–15 seconds on a typical machine. The array version takes under 0.2 seconds because it makes exactly two calls to the Excel object model — one read and one write — instead of 40,000 individual cell interactions. The performance difference scales linearly with row count.
Example 2: Filtering Data in Memory
Reading data, filtering to matching rows, and writing only the matches avoids the need to use AutoFilter or Advanced Filter via VBA — both slower and more fragile. Instead, read the source into an array, collect matching rows into a second dynamic array, then write the collected rows in one operation. This approach also preserves the original data completely untouched, which is specifically important for audit compliance and undo requirements.
Example 3: Sorting an Array
VBA has no built-in array sort function. For small arrays (under a few hundred elements), bubble sort is sufficient and straightforward to implement. For larger arrays, a quicksort is significantly faster. Sorting in memory and writing the sorted result back to the sheet in one operation is still faster than any approach involving repeated cell reads during comparison, because all the comparison work happens at VBA speed without any COM overhead.
Example 4: Multi-Dimensional Array — Build a Summary Grid
A two-dimensional array models a grid directly — row and column indices map to a report layout. This example reads a transaction table and builds a 12×5 summary grid (12 months × 5 regions) by accumulating values into the correct array cell based on the month and region in each source row. Writing the completed grid to the sheet takes one call regardless of how many transactions were processed — consequently, the output is always fast even on very large source datasets.
Example 5: ReDim Preserve — Growing an Array Dynamically
ReDim Preserve resizes a dynamic array without losing existing values — specifically growing the last dimension only. This is useful when the final output count is not known before processing begins. However, ReDim Preserve inside a tight loop is slow because it copies the entire array on each resize. The best practice is to pre-size to an estimated maximum, then ReDim Preserve once at the end to trim to the actual count. This pattern avoids repeated copying while still adapting to the real data size.
Example 6: Array of Arrays — Jagged Structures
VBA does not natively support jagged arrays (arrays where each row has a different length), but a Variant array of Variant arrays achieves the same result. Each element of the outer array holds an inner array of varying size. This is particularly useful for grouping data rows by category where each category has a different number of members — for example, grouping order lines by customer ID before processing each customer's batch separately in memory.
Troubleshooting VBA Arrays
All three issues below are the most common VBA array errors. Each has a specific cause that is identifiable in under one minute.
Subscript out of range — Run-time error 9
This error means you accessed an index outside the array's declared bounds. First, check the actual bounds using LBound(arr) and UBound(arr) in the Immediate Window (Ctrl+G in the VBA editor). When reading a Range into a Variant array, bounds are always (1 To rowCount, 1 To colCount) regardless of where the range sits on the sheet — row 500 in the sheet becomes index 1 in the array. Also check whether a single-column range read produced a 2D array when you expected 1D — use Application.Transpose to convert between them.
ReDim Preserve fails with type mismatch or subscript error
ReDim Preserve can only resize the last dimension of a multi-dimensional array. If you try to resize the first dimension of a 2D array, VBA raises an error. The solution is to declare 2D arrays with the dimension you want to extend as the last dimension — for example, (1 To colCount, 1 To rowCount) if rows need dynamic resizing. Alternatively, restructure the logic to collect into a 1D array whose final size is known before ReDim Preserve, or use a separate collection step followed by a single sized allocation at the end.
Writing an array back to the sheet produces #VALUE! in cells
When you assign an array to a Range's Value property, the range dimensions must exactly match the array dimensions. If the array dimensions are mismatched — for example, assigning a 1D horizontal array to a vertical range — the cells show #VALUE!. Use Resize to make the output range match the array precisely: Range("A1").Resize(UBound(arr,1), UBound(arr,2)).Value = arr. For a 1D array written to a column, transpose it first: Range("A1").Resize(UBound(arr)).Value = Application.Transpose(arr).
Frequently Asked Questions
- How do I read a range into an array in VBA?+Declare a Variant variable and assign the Range's Value property to it: Dim data As Variant, then data = Range("A1:D1000").Value. The result is a 2D array where the first index is the row (1 to 1000) and the second is the column (1 to 4), regardless of where the range sits on the sheet. A single-column range also produces a 2D array — data(row, 1) — not a 1D array. To get a 1D array from a single row or column, use Application.Transpose on the result to convert the dimensions.
- Why are arrays so much faster than reading cells in a loop?+Each call to Cells(i, j).Value crosses the boundary between the VBA runtime and the Excel COM object model, involving marshalling, type conversion, and context switching. This overhead is small per call but accumulates to seconds or minutes across tens of thousands of calls. Reading a range into an array makes one crossing to transfer all values at once, then all loop iterations operate in plain VBA memory with no COM overhead. Writing the array back makes one more crossing — total overhead is two COM calls regardless of how many rows are processed.
- What is the difference between a fixed-size and dynamic array in VBA?+A fixed-size array has its dimensions declared at compile time and cannot be resized: Dim scores(1 To 100) As Double. A dynamic array is declared without dimensions and resized at runtime using ReDim: Dim scores() As Double, then ReDim scores(1 To n) when n is known. Use fixed-size arrays when the count is constant and known at design time. Use dynamic arrays when the count depends on data — for example, the number of rows in a worksheet that changes with each run. ReDim Preserve resizes a dynamic array while keeping existing values, but can only extend the last dimension.
- How do I write a VBA array back to a worksheet range?+Assign the array to a range's Value property. The range must match the array dimensions exactly — use Resize to ensure this: Range("A1").Resize(UBound(arr, 1), UBound(arr, 2)).Value = arr. For a 1D array being written to a column, transpose it first: Range("A1").Resize(UBound(arr)).Value = Application.Transpose(arr). Writing a correctly sized array to a range is a single COM call regardless of array size, making it dramatically faster than writing values to cells individually in a loop.