🚀The world's best VBA AI has evolved. ExcelMaster is now an autonomous Agent.Read more →
Back to Blog

VBA DoEvents in Excel — Stop Excel Going Not Responding (and Why It Lets Your Macro Run Twice)

|

VBA DoEvents in Excel — Stop Excel Going Not Responding (and Why It Lets Your Macro Run Twice)

TL;DRDoEvents pauses your macro for a split second and lets Excel handle the clicks, keystrokes, and repaints that queued up while your code was busy. That is what keeps the window from greying out to "Not Responding," and it is what makes a working Cancel button possible. But the same yield hands control back to the user mid-macro — so they can click your button again and launch a second copy of the macro inside the first. Guard that re-entrancy with a running flag, and call DoEvents on an interval, not every iteration:

Private mRunning As Boolean

Sub LongJob()
    If mRunning Then Exit Sub         ' re-entrancy guard - refuse to start a second copy
    mRunning = True
    On Error GoTo CleanExit
    Dim i As Long
    For i = 1 To 500000
        ' ... work on row i ...
        If i Mod 1000 = 0 Then DoEvents   ' yield occasionally so Excel stays alive
    Next i
CleanExit:
    mRunning = False                  ' always clear the flag, even after an error
End Sub

A long VBA loop runs on Excel's one and only thread, and while it runs Excel cannot do anything else — not repaint, not register a click, not update the title bar. After a few seconds Windows decides the app is hung and stamps "Not Responding" across it, even though your macro is working perfectly. DoEvents is the escape valve. This guide is built on one idea — DoEvents buys responsiveness by giving control back, and "giving control back" includes giving the user the control to break things. It is not a free "make Excel smooth" line; it is a trade, and you have to pay the re-entrancy side of it.

What you'll learn

  • The mental model — one thread, a message queue, and why a loop freezes the window
  • What DoEvents actually does — drain the queue, then resume
  • The real hazard — re-entrancy, and the running-flag guard that stops it
  • Why you must throttle it — DoEvents every iteration can dominate your runtime
  • How it powers a cancel button, and how that connects to a status bar
  • Why DoEvents is not multithreading, and when to just leave it out

The mental model: one thread and a queue that never gets read

Excel runs your VBA on the same single thread it uses for everything else — drawing the grid, handling your mouse, refreshing the ribbon. While a macro is running, that thread is 100% yours. Every click and keystroke the user makes does not vanish; it lands in a message queue and waits. But nothing is reading the queue, because the thread is busy in your loop. The screen goes stale, the queue backs up, and after a few seconds Windows paints "Not Responding" over the window.

DoEvents reads the queue. When you call it, VBA pauses your macro, lets Excel process everything waiting — repaint the screen, handle the clicks, run any triggered event handlers — and then returns control to the line after DoEvents so your macro continues.

' ... your loop is hogging the thread; the window is frozen ...
DoEvents   ' Excel drains the queue: repaints, processes clicks, runs handlers, then returns
' ... your macro resumes here ...

That is genuinely useful: the window stays responsive, the status bar you set actually repaints, and the user can interact. The problem is exactly that last part.

The real hazard: re-entrancy

Here is the failure that makes DoEvents dangerous rather than merely slow. Your macro is launched by a button. Halfway through, you call DoEvents. Excel processes the queued input — and the user, seeing the macro "taking a while," has clicked the same button again. That click is now handled during your DoEvents, so Excel starts a second run of the macro while the first is still paused inside the loop. Two copies now interleave on the same data.

The results range from wrong to catastrophic: rows processed twice, a counter that double-counts, a file written by both runs, or a flat-out error when the second run modifies state the first run assumed was stable. This is re-entrancy, and it is the number-one DoEvents bug — the same failure family as an event handler that triggers itself.

The guard is a module-level flag that refuses a second entry:

Private mRunning As Boolean

Sub LongJob()
    If mRunning Then Exit Sub    ' already running - ignore the extra click
    mRunning = True
    On Error GoTo CleanExit
    ' ... loop with DoEvents ...
CleanExit:
    mRunning = False             ' clear on success AND on error, or you lock yourself out
End Sub

Note the CleanExit restore is not optional here either: if an error skips mRunning = False, the flag stays True and the macro refuses to ever run again until you reset the project. Same On Error discipline as every switch in this cluster.

Throttle it: DoEvents is not free

Even without re-entrancy, DoEvents has a cost. Draining the message queue and yielding to the OS takes real time — often far more than the tiny piece of work in one loop iteration. Call it every iteration of a tight loop and you can turn a 2-second macro into a 30-second one, having spent most of the time yielding instead of working.

So call it on an interval — every 1,000 rows, or every quarter-second on a timer — not every pass:

For i = 1 To n
    ' ... work ...
    If i Mod 1000 = 0 Then DoEvents   ' responsive enough, without paying on every row
Next i

The interval is a dial: more frequent means a snappier window and a more responsive cancel button; less frequent means faster raw throughput. i Mod 1000 is a good starting point for fast per-row work; tune it to how long each iteration takes.

The cancel button it makes possible

The upside re-entrancy shows you is also the feature people want most: because DoEvents lets Excel process clicks mid-run, a user can click a Cancel button and have it register while the macro is still looping. Wire a public flag to the button and check it after each DoEvents:

Public gCancel As Boolean          ' set to True by a Cancel button's click handler

Sub LongJob()
    Dim i As Long
    For i = 1 To 500000
        ' ... work ...
        If i Mod 1000 = 0 Then
            DoEvents
            If gCancel Then Exit For   ' the click got through - stop cleanly
        End If
    Next i
End Sub

Without DoEvents, the Cancel click just sits in the queue until the macro finishes on its own — useless. With it, the button works. Pair it with a status bar message so the user can see progress and stop it.

Why it is not multithreading — and when to skip it

DoEvents does not run your macro in the background or on another thread. Everything is still serial on the one thread; DoEvents just interleaves Excel's pending work between chunks of yours. Your loop does not speed up — if anything it slows down — it only stops blocking the UI. If you actually need work to run in parallel, that is a different tool entirely (a separate process, or a language that threads), not DoEvents.

And the honest default: if a macro finishes in under a second or two, it never triggers "Not Responding," so DoEvents adds cost and re-entrancy risk for no benefit — leave it out. Reach for it only when the run is long enough that a frozen window is a real problem, and once you do, guard re-entrancy and throttle the calls. A fast macro with DoEvents sprinkled through its loop is slower and more fragile than the same macro without it.

How ExcelMaster helps

Using DoEvents well is a bundle of judgment calls: only on long runs, on an interval not every iteration, behind a re-entrancy guard, with the flag cleared in an error handler, and paired with a cancel check and a progress message. Get any one wrong and you get a macro that runs twice, or one that crawls, or a Cancel button that never fires.

ExcelMaster makes those calls for you. Ask it for "a long import that stays responsive and can be cancelled," and it adds a running flag to block re-entrancy, calls DoEvents on a sensible interval, checks a cancel flag right after, and clears everything in a CleanExit handler — so you get a responsive, interruptible macro instead of a frozen window or a double-run.

Frequently asked questions

What does DoEvents do in Excel VBA?

DoEvents pauses your macro briefly and lets Excel process the input and repaint work that queued up while your code was running — clicks, keystrokes, screen redraws, and any triggered event handlers. Then it returns control to the next line and your macro continues. It is what keeps a long macro from freezing the Excel window into "Not Responding."

Why does Excel say "Not Responding" while my macro runs?

Because your macro is using Excel's single thread, so Excel cannot repaint or handle input until the macro yields. After a few seconds Windows marks the app as hung — even though the macro is working fine. Adding DoEvents on an interval inside your loop lets Excel breathe, and the "Not Responding" label goes away.

Is DoEvents dangerous?

It can be, through re-entrancy. Because DoEvents lets Excel process clicks mid-run, a user can re-launch the same macro (by clicking its button again) while the first run is paused, so two copies run at once and corrupt each other's work. Guard against it with a module-level "running" flag that makes the macro exit if it is already running, and clear the flag in an error handler.

Should I call DoEvents in every loop iteration?

No — throttle it. DoEvents has real overhead, and calling it every iteration of a tight loop can dominate your runtime and make the macro far slower. Call it on an interval instead, such as If i Mod 1000 = 0 Then DoEvents, and tune the interval to how long each iteration takes.

Does DoEvents make my macro multithreaded or faster?

No. Everything still runs serially on one thread; DoEvents only interleaves Excel's pending UI work between chunks of your code. Your loop does not speed up — it usually slows down slightly — it just stops blocking the interface. For true parallel work you need a separate process, not DoEvents.

Tested in

Tested in: Excel 365 (Windows 11), VBA 7.1 — last verified 2026-08-18.

Related guides: VBA StatusBar · VBA DisplayAlerts · VBA ScreenUpdating · VBA EnableEvents · VBA On Error