TL;DR —
DoEventspauses 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 callDoEventson 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
DoEventsactually does — drain the queue, then resume - The real hazard — re-entrancy, and the running-flag guard that stops it
- Why you must throttle it —
DoEventsevery iteration can dominate your runtime - How it powers a cancel button, and how that connects to a status bar
- Why
DoEventsis 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
