skip to content

In Vue 3, what does one scheduler flush run, in what order, and when does an awaited nextTick() resume relative to it?

level: middleimportance: should knowfreq 40%

answer

  1. one sorted queue, one post list
  2. pre jobs sit before their component
  3. parents before children
  4. the flush promise resolves last

basics

~20 s

A Vue 3 flush runs the main queue sorted by component id, with pre-flush watcher jobs just before their component's update, then the post-flush callbacks such as mounted and updated hooks; nextTick() returns that flush's promise, so it resumes after all of it.

solid answer

~40 s

The first queued job schedules `flushJobs` as a microtask. It walks the **main queue**, sorted by component uid, so parents update before children; pre-flush jobs — watchers with the default timing — carry their component's id and a pre flag, which places each just before that component's update job, and disposed jobs of unmounted components are skipped. Then it runs the **post-flush list**, deduplicated and sorted: `onMounted` and `onUpdated` hooks, post-flush watchers, template ref assignments. If any of that queued more work, it flushes again. `nextTick()` returns the promise of the pending flush, so code after `await nextTick()` runs after the whole flush, post callbacks included; with nothing pending it returns an already-resolved promise.

go deeper

for a junior

Know that Vue updates components, then runs hooks like onUpdated, and that code after await nextTick() runs after all of that.

for a middle

Explain the sorted main queue, where pre-flush watcher jobs are placed, the post-flush list, and that nextTick returns the pending flush promise.

for a senior

Use the flush order to explain why a watcher saw stale DOM, why a hook ran before your nextTick code, and how a hook that writes state triggers another flush.

for a principal

Decide where timing-sensitive logic belongs (watcher, hook or after nextTick) as a team convention, so ordering bugs are designed out rather than debugged.

## Two structures, one flush Vue 3's scheduler (in `runtime-core`) keeps two collections: - the **main queue** — component update jobs and pre-flush jobs, kept **sorted by id**, where a component's id is its uid; - the **post-flush list** — callbacks that must run after the DOM has been patched. There is no separate array for pre-flush work: a pre-flush job (a watcher using the default timing) is given its component's id and a pre flag, and the insertion logic places it immediately **before** that component's update job. Pre-flush jobs that belong to no component get the lowest possible id and run first. The first `queueJob` or post-flush registration schedules the flush with a resolved promise's `then`, so it runs as a **microtask** once the current synchronous code has finished. ## The order inside flushJobs 1. **Main queue, in id order.** For each job: skip it if it was disposed (its component was unmounted earlier in the flush), otherwise run it. A component's update job re-renders it and patches its DOM. Because parents are created before children, a parent's lower uid means it updates first; a child it patched along the way finds nothing dirty when its own job comes up. 2. **Clean-up of the queue.** Queued flags are cleared and the queue is emptied. 3. **Post-flush list.** Deduplicated with a `Set`, sorted by id, then run: `onMounted` for newly created components, `onUpdated` hooks, watchers with post timing, template ref assignments, `onUnmounted` hooks. 4. **Re-flush if needed.** If steps 1–3 queued more jobs — an `onUpdated` hook that changes state, say — `flushJobs` runs again before returning. Only after that does the flush's promise resolve. | Phase | Typical contents | Sees the updated DOM? | |---|---|---| | pre-flush jobs | default-timing watchers | no — they run before their component renders | | component updates | render + patch, parent to child | produces it | | post-flush list | mounted/updated hooks, post watchers, template refs | yes | | after `await nextTick()` | your code | yes, and post callbacks have run | ## What nextTick() returns `nextTick()` returns the promise of the flush that is currently scheduled, if any; otherwise an already-resolved promise. With a callback, `nextTick(fn)` chains `fn` onto that same promise. Consequences: - after a state change, `await nextTick()` resumes **after** every phase above, including post-flush callbacks; - called when nothing is pending, it only defers to the next microtask — it does not wait for future changes; - in the Options API, `this.$nextTick(fn)` is the same function bound to the component instance. ## Why the order is designed this way - **Parent first** avoids rendering a child with props that are about to change, and lets the parent's update skip or remove children whose jobs are still queued. - **Pre jobs before their component** let a watcher adjust state that the component is about to render, so the render sees the adjusted value in the same flush. - **Post list last** guarantees hooks and post watchers observe a fully patched DOM for every component in the flush, not just their own. ## Where this shows up in interviews - 'Why can a default-timing watcher not read the updated DOM?' — it runs before its component's update job. - 'Does `await nextTick()` run before or after `onUpdated`?' — after: `onUpdated` is in the post-flush list, which runs before the flush promise resolves. - 'What if an `onUpdated` hook writes state?' — more work is queued and the flush runs again; a hook that always writes can loop, which the development build detects.

  • In Vue 3, does code after `await nextTick()` run before or after onUpdated hooks from that flush?
    After. `onUpdated` hooks are queued on the post-flush list, which the flush runs before its promise resolves; `nextTick()` returns that promise, so its continuation runs once the whole flush, post callbacks included, has finished.
  • Why does a watcher with the default timing see the new state but the old DOM?
    It is queued as a pre-flush job with its component's id, which places it just before that component's update job in the sorted queue. The state has already changed, but the component has not re-rendered yet, so the DOM still reflects the previous render.

saying these in an interview costs you the question

  • Vue keeps separate pre and post arrays that each run independently
  • Children update before parents so props bubble up
  • nextTick() resolves before onUpdated hooks run
  • nextTick() always waits for some future DOM update
  • The flush runs in a setTimeout, after rendering and painting