Do While vs. Do Until Loops in Excel VBA

Excel VBA’s Do While and Do Until loops repeat work until a condition tells them to stop, such as reaching a target or finishing a queue.

Do While keeps going while its condition is true. Do Until keeps going while its condition is false. You can often express the same stopping rule either way.

The position of that condition matters too. Checking it before the work can skip the loop entirely. Checking it afterward means the work runs at least once.

In this tutorial, I’ll compare the four syntax forms, show when a loop runs zero times, and use capacity limits and blank rows to control processing.

Do While and Do Until Syntax

Choose While when the condition describes when to continue. Choose Until when it describes when to stop.

These are the four condition placements supported by VBA’s Do…Loop statement:

FormCondition CheckedKeep Running When
Do While condition ... LoopBefore the bodyCondition is True
Do Until condition ... LoopBefore the bodyCondition is False
Do ... Loop While conditionAfter the bodyCondition is True
Do ... Loop Until conditionAfter the bodyCondition is False

The dots in this table stand for the statements you repeat. They aren’t VBA code to paste into the editor.

These are four forms of the same control structure. The examples below show when their results match and when they differ.

Running the Workbook Examples

The downloadable workbook contains completed results on four sheets: Queue totals, Condition timing, Capacity stop, and Blank rows.

The download is a regular .xlsx file with the saved results. It holds no code, so paste the code from this article into a module.

Follow these steps in Windows desktop Excel:

  1. Open VBA Do While and Do Until - SpreadsheetPlanet.xlsx. If you see the yellow bar, click Enable Editing.
  2. Save a copy as an Excel Macro-Enabled Workbook (.xlsm).
  3. Press Alt + F11, then choose Insert > Module.
  4. Paste all four code blocks below into that one module, in order.
  5. Return to Excel, press Alt + F8, select RunAllExamples, and click Run.

RunAllExamples refreshes every example’s output. You can also run RunQueueTotals, RunConditionTiming, RunCapacityStop, or RunBlankRows individually.

The code uses ThisWorkbook, meaning the workbook containing the module. Keep the four worksheet names exactly as supplied.

The first block includes RunAllExamples, which calls procedures from the other three blocks. Paste all four before you run it.

If the new module already starts with Option Explicit, skip that line in the first block.

Example 1: Comparing While and Until Conditions

On Queue totals, cells A2:B7 list six archive boxes with 18, 25, 12, 30, 15, and 20 pages. I’ll total them using each form.

Six archive boxes and their page counts before comparing the four loop forms

The first form starts at row 2 and continues while rowNum <= lastRow. The second stops when rowNum > lastRow becomes true.

Those conditions are opposites. Once the row counter passes the last source row, both forms stop.

Here is the complete queue procedure, plus RunAllExamples, which runs all four examples.

Option Explicit

Public Sub RunAllExamples()
    RunQueueTotals
    RunConditionTiming
    RunCapacityStop
    RunBlankRows
End Sub

Public Sub RunQueueTotals()
    Dim ws As Worksheet
    Dim lastRow As Long, rowNum As Long, pages As Long, boxes As Long
    Set ws = ThisWorkbook.Worksheets("Queue totals")
    lastRow = ws.Cells(ws.Rows.Count, "A").End(xlUp).Row

    rowNum = 2: pages = 0: boxes = 0
    Do While rowNum <= lastRow
        pages = pages + ws.Cells(rowNum, "B").Value2
        boxes = boxes + 1
        rowNum = rowNum + 1
    Loop
    ws.Range("E2").Value2 = pages
    ws.Range("F2").Value2 = boxes

    rowNum = 2: pages = 0: boxes = 0
    Do Until rowNum > lastRow
        pages = pages + ws.Cells(rowNum, "B").Value2
        boxes = boxes + 1
        rowNum = rowNum + 1
    Loop
    ws.Range("E3").Value2 = pages
    ws.Range("F3").Value2 = boxes

    rowNum = 2: pages = 0: boxes = 0
    'Bottom-tested forms need this guard when there are no data rows.
    If lastRow >= 2 Then
        Do
            pages = pages + ws.Cells(rowNum, "B").Value2
            boxes = boxes + 1
            rowNum = rowNum + 1
        Loop While rowNum <= lastRow
    End If
    ws.Range("E4").Value2 = pages
    ws.Range("F4").Value2 = boxes

    rowNum = 2: pages = 0: boxes = 0
    If lastRow >= 2 Then
        Do
            pages = pages + ws.Cells(rowNum, "B").Value2
            boxes = boxes + 1
            rowNum = rowNum + 1
        Loop Until rowNum > lastRow
    End If
    ws.Range("E5").Value2 = pages
    ws.Range("F5").Value2 = boxes
End Sub

Run RunQueueTotals to fill E2:F5. Each form returns 120 pages and 6 boxes.

All four loop forms return 120 pages and six boxes from the same queue

lastRow finds the last used cell in column A. The loop reads each page count from column B, adds it to pages, and increments boxes.

rowNum = rowNum + 1 advances to the next row. Without that update, the condition would keep testing the same row.

The variables reset before each form. Otherwise, the second form would start with the first form’s accumulated totals.

After processing row 7, rowNum becomes 8. The While condition is now false, and the Until condition is true.

The two bottom-tested forms put the condition after the body. An outer If lastRow >= 2 Then prevents them from entering when the queue has no data.

That guard doesn’t change bottom-tested loop behavior. If execution reaches Do, the body still runs before its first condition check.

I usually start with a top-tested loop for worksheet processing. It naturally handles a task that has no rows to process.

Example 2: Checking Before or After the Body

On Condition timing, A1:D3 contains two cases. Both target 100 pages in batches of 10. One starts at 70; the other starts at 100.

Two starting conditions targeting 100 pages in batches of ten, beginning at 70 and 100 pages

Starting at 70, every form adds three batches to reach 100. Starting at 100 exposes the difference between checking before and checking after.

These procedures include the input check, all four timing functions, and the procedure that writes their results:

Private Sub ValidateBatchInputs(ByVal startPages As Long, ByVal targetPages As Long, ByVal batchSize As Long)
    If startPages < 0 Or targetPages < 0 Or batchSize <= 0 Then
        Err.Raise vbObjectError + 1000, "DoLoopExamples", _
                  "Start and target must be nonnegative; batch size must be positive."
    End If
End Sub

Public Function BatchTopWhile(ByVal startPages As Long, ByVal targetPages As Long, ByVal batchSize As Long) As Variant
    Dim pages As Long, runs As Long
    ValidateBatchInputs startPages, targetPages, batchSize
    pages = startPages: runs = 0
    Do While pages < targetPages
        pages = pages + batchSize
        runs = runs + 1
    Loop
    BatchTopWhile = Array(runs, pages)
End Function

Public Function BatchTopUntil(ByVal startPages As Long, ByVal targetPages As Long, ByVal batchSize As Long) As Variant
    Dim pages As Long, runs As Long
    ValidateBatchInputs startPages, targetPages, batchSize
    pages = startPages: runs = 0
    Do Until pages >= targetPages
        pages = pages + batchSize
        runs = runs + 1
    Loop
    BatchTopUntil = Array(runs, pages)
End Function

Public Function BatchBottomWhile(ByVal startPages As Long, ByVal targetPages As Long, ByVal batchSize As Long) As Variant
    Dim pages As Long, runs As Long
    ValidateBatchInputs startPages, targetPages, batchSize
    pages = startPages: runs = 0
    Do
        pages = pages + batchSize
        runs = runs + 1
    Loop While pages < targetPages
    BatchBottomWhile = Array(runs, pages)
End Function

Public Function BatchBottomUntil(ByVal startPages As Long, ByVal targetPages As Long, ByVal batchSize As Long) As Variant
    Dim pages As Long, runs As Long
    ValidateBatchInputs startPages, targetPages, batchSize
    pages = startPages: runs = 0
    Do
        pages = pages + batchSize
        runs = runs + 1
    Loop Until pages >= targetPages
    BatchBottomUntil = Array(runs, pages)
End Function

Public Sub RunConditionTiming()
    Dim ws As Worksheet, sourceRow As Long, formIndex As Long, outputRow As Long
    Dim startPages As Long, targetPages As Long, batchSize As Long, answer As Variant
    Set ws = ThisWorkbook.Worksheets("Condition timing")
    outputRow = 2
    For formIndex = 1 To 4
        For sourceRow = 2 To 3
            startPages = ws.Cells(sourceRow, "B").Value2
            targetPages = ws.Cells(sourceRow, "C").Value2
            batchSize = ws.Cells(sourceRow, "D").Value2
            Select Case formIndex
                Case 1: answer = BatchTopWhile(startPages, targetPages, batchSize)
                Case 2: answer = BatchTopUntil(startPages, targetPages, batchSize)
                Case 3: answer = BatchBottomWhile(startPages, targetPages, batchSize)
                Case 4: answer = BatchBottomUntil(startPages, targetPages, batchSize)
            End Select
            ws.Cells(outputRow, "H").Value2 = answer(0)
            ws.Cells(outputRow, "I").Value2 = answer(1)
            outputRow = outputRow + 1
        Next sourceRow
    Next formIndex
End Sub

Run RunConditionTiming. The results appear in H2:I9, beside the loop and case labels in columns F:G.

When the target is already met, top-tested loops run zero times while bottom-tested loops run once

For the Below target case, the running page counts are 80, 90, and 100. All four forms record three runs and 100 final pages.

For Target met, the two top-tested forms record zero runs and leave the total at 100.

The bottom-tested forms add 10 before checking. They record one run and finish at 110, even though the starting value already met the target.

Use a bottom-tested form when that first run is intentional.

Each timing function returns Array(runs, pages). RunConditionTiming reads answer(0) for the run count and answer(1) for the final page total.

The While versions use pages < targetPages. The Until versions use its opposite, pages >= targetPages.

Using >= also allows a batch to pass the target. An equality-only stop can miss the target when the batch size doesn’t divide the remaining amount.

For these examples, start and target must be nonnegative whole numbers. Batch size must be a positive whole number.

ValidateBatchInputs rejects negative starts or targets and nonpositive batch sizes. It doesn’t check every possible worksheet input before VBA converts it to Long.

Keep text, error values, decimals, and very large numbers out of these sample inputs. The procedures aren’t a general input-validation system.

Example 3: Leaving Early with Exit Do

On Capacity stop, A2:B7 contains the same six boxes. Cell E1 sets a 70-page capacity. I’ll accept boxes in queue order without exceeding it.

Six queued archive boxes with a capacity limit of 70 pages

The first three boxes total 55 pages. Adding BX-404’s 30 pages would make 85, so the code stops before adding that box.

Here is the complete procedure:

Public Sub RunCapacityStop()
    Dim ws As Worksheet
    Dim rowNum As Long, lastRow As Long, pages As Long, boxes As Long
    Dim capacity As Long, nextPages As Long, deferredBox As String, exitReason As String
    Set ws = ThisWorkbook.Worksheets("Capacity stop")
    capacity = ws.Range("E1").Value2
    If capacity < 0 Then Err.Raise vbObjectError + 1001, "DoLoopExamples", "Capacity must be nonnegative."
    lastRow = ws.Cells(ws.Rows.Count, "A").End(xlUp).Row
    rowNum = 2: pages = 0: boxes = 0
    deferredBox = "-": exitReason = "All boxes fit"
    Do While rowNum <= lastRow
        nextPages = ws.Cells(rowNum, "B").Value2
        If pages + nextPages > capacity Then
            deferredBox = CStr(ws.Cells(rowNum, "A").Value2)
            exitReason = "Next box exceeds capacity"
            Exit Do
        End If
        pages = pages + nextPages
        boxes = boxes + 1
        rowNum = rowNum + 1
    Loop
    ws.Range("E4").Value2 = pages
    ws.Range("E5").Value2 = boxes
    ws.Range("E6").Value2 = deferredBox
    ws.Range("E7").Value2 = exitReason
End Sub

Run RunCapacityStop. Cells E4:E7 show 55 pages, 3 boxes, BX-404, and Next box exceeds capacity.

Exit Do results accepting 55 pages across three boxes and deferring BX-404 because it exceeds capacity

The row-bound condition controls the normal loop. Inside it, the capacity check supplies another reason to stop.

Exit Do leaves the loop immediately. Execution continues with the output assignments after Loop, rather than ending the entire procedure.

The position of that check matters. It runs before pages and boxes increase, so the deferred box never enters the accepted totals.

This example preserves queue order. It doesn’t skip BX-404 to find a later box that fits, and it doesn’t delete or change source rows.

Capacity must be a nonnegative whole number. The procedure rejects a negative capacity. As with the timing example, arbitrary input validation needs additional code.

Example 4: Handling Blank Rows Deliberately

On Blank rows, A2:B8 contains the six boxes with an intentional gap at row 4. I’ll compare stopping at that gap with skipping it.

The archive box queue with an intentional blank row between the second and third boxes

A blank row can mean the queue has ended. It can also be a gap between valid records. Your code needs to choose which meaning applies.

This procedure runs both policies:

Public Sub RunBlankRows()
    Dim ws As Worksheet
    Dim rowNum As Long, lastRow As Long, pages As Long, boxes As Long, lastDataRow As Long
    Set ws = ThisWorkbook.Worksheets("Blank rows")
    lastRow = ws.Cells(ws.Rows.Count, "A").End(xlUp).Row

    rowNum = 2: pages = 0: boxes = 0: lastDataRow = 0
    Do While rowNum <= lastRow
        If Len(Trim$(CStr(ws.Cells(rowNum, "A").Value2))) = 0 Then Exit Do
        pages = pages + ws.Cells(rowNum, "B").Value2
        boxes = boxes + 1
        lastDataRow = rowNum
        rowNum = rowNum + 1
    Loop
    ws.Range("E2").Value2 = pages
    ws.Range("F2").Value2 = boxes
    ws.Range("G2").Value2 = lastDataRow

    rowNum = 2: pages = 0: boxes = 0: lastDataRow = 0
    Do Until rowNum > lastRow
        If Len(Trim$(CStr(ws.Cells(rowNum, "A").Value2))) > 0 Then
            pages = pages + ws.Cells(rowNum, "B").Value2
            boxes = boxes + 1
            lastDataRow = rowNum
        End If
        'Advance for every row, including rows deliberately skipped.
        rowNum = rowNum + 1
    Loop
    ws.Range("E3").Value2 = pages
    ws.Range("F3").Value2 = boxes
    ws.Range("G3").Value2 = lastDataRow
End Sub

Run RunBlankRows. The first policy returns 43 pages, 2 boxes, and last data row 3 in E2:G2.

The bounded policy returns 120 pages, 6 boxes, and last data row 8 in E3:G3.

Stopping at the blank totals 43 pages; skipping it totals 120 pages across six boxes

The first loop treats an empty box ID as an end marker, also called a sentinel. It exits at row 4 before reaching the remaining boxes.

The second loop uses the last source row as its boundary. It adds a row only when the box ID has characters after trimming spaces.

That policy skips empty IDs, IDs containing only ordinary spaces, and formulas returning empty text. It doesn’t try to clean every possible kind of whitespace.

Notice that rowNum increases outside the If block. A skipped row must still advance the counter, or the loop will keep revisiting it.

These totals differ because the blank-row policies differ. Either While or Until can implement either policy with the appropriate condition.

Note: The gap is intentional on the Blank rows sheet. Keep Queue totals and Capacity stop contiguous because their procedures count every row through the last box ID.

Additional Notes About Do Loops in Excel VBA

When adapting these examples, check the assumptions behind the stopping condition:

  • Update a value that affects the condition on every relevant pass. A condition that never changes can leave the loop running indefinitely.
  • Keep page counts as nonnegative whole numbers and box IDs as text. The sample uses Long arithmetic and doesn’t handle unlimited totals or arbitrary data types.
  • The code uses ThisWorkbook, so it runs against the workbook that holds the module. If you paste it into another file, keep the four sheet names exactly as supplied.
  • The saved results are snapshots. Changing an input doesn’t update them, so run the matching macro again to refresh the results.

Frequently Asked Questions

These questions cover a few other choices you may face when adapting a Do loop.

Can I Put a Condition After Both Do and Loop?

Use one condition, either at the top or at the bottom. If you need another stopping rule inside the body, check it with If and use Exit Do.

Which Loop Does Exit Do Leave When Loops Are Nested?

It leaves the innermost enclosing Do loop. The outer loop continues from the statement after that inner loop, subject to its own stopping logic.

How Can I Interrupt a Loop That Won’t Stop?

Microsoft lists Esc or Ctrl + Break for interrupting an endless loop. After stopping, fix the condition or missing state update before running it again.

Can I Run These Macros in Excel for the Web?

No. Excel for the web can’t create, run, or edit VBA macros. Open the macro-enabled copy in desktop Excel to run them.

Conclusion

I’ve shown how Do While and Do Until differ in condition logic and timing, and used them for queue totals, batch processing, early exits, and blank rows.

I hope you found this article helpful.

Other Excel articles you may also like:

Leave a Comment