In Vue 3, what does one scheduler flush run, in what order, and when does an awaited nextTick() resume relative to it?
answer
- one sorted queue, one post list
- pre jobs sit before their component
- parents before children
- the flush promise resolves last
basics
~20 sA 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 sThe 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
Know that Vue updates components, then runs hooks like onUpdated, and that code after await nextTick() runs after all of that.
Explain the sorted main queue, where pre-flush watcher jobs are placed, the post-flush list, and that nextTick returns the pending flush promise.
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.
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