skip to content

Rendering Mechanism

Vue compiles templates into render functions that produce vnodes, then patches the DOM per component when dependencies change. Interviewers probe how the compiler helps the runtime.

part ofVue.jsoverview, primer and where to startread it →
on this pageshow

explore

questions

17

A Vue 3 parent renders three child components and changes a prop passed to only one of them; which components re-render, and why?

level: middleimportance: must knowfreq 62%

answer

  1. one render effect per component
  2. parent read the changed state
  3. props compared when patching a child
  4. siblings skipped, not re-rendered

basics

~20 s

The parent re-renders because its render read the changed state. The child whose prop changed re-renders. The two siblings are reached by the patch but skipped, because their props compare equal, so their render functions and descendants do not run.

solid answer

~40 s

Every Vue 3 component instance has its **own render effect**. The parent's template read the value it passes down, so changing it re-runs the **parent's** render and produces new child vnodes. While patching each child component vnode, the renderer calls `shouldUpdateComponent`: with compiled templates it compares only the props the compiler marked dynamic, value by value with `!==`, and also forces an update for dynamic slot content or a directive or transition on the component vnode. The changed child gets its render run; the unchanged siblings just receive the new vnode and keep their DOM. The same check repeats one level down, so a grandchild re-renders only if its props changed too. If the state were read only inside the child, only the child would re-render — the parent never tracked it.

go deeper

for a junior

Recall that each Vue component re-renders on its own, and that a parent's update does not automatically re-render every child.

for a middle

Walk through the per-component render effect and the props comparison at each child boundary, including which cases force a child update.

for a senior

Use this model to explain unexpected updates in production, such as inline object props or dynamic slots causing children to re-render every time.

for a principal

Treat component boundaries as the update granularity when reviewing architecture: where state lives decides which subtrees pay for a change.

## The setup Assume a `<script setup>` parent that owns `count` and renders three children: ```vue <script setup lang="ts"> import { ref } from 'vue' import Stat from './Stat.vue' const count = ref(0) const total = ref(10) </script> <template> <Stat label="Count" :value="count" /> <Stat label="Total" :value="total" /> <Stat label="Static" value="n/a" /> <button @click="count++">add</button> </template> ``` Clicking the button changes only `count`. The question is which render functions run. ## One render effect per component In Vue 3 each component instance gets exactly one **render effect** — a reactive effect wrapping the function that renders it and patches the result. The effect remembers which reactive values were read during the last render. Two consequences: - A component re-renders when **something it read** changes, regardless of where that state lives. - A component does **not** re-render merely because its parent did; the parent has to decide, per child, that something passed in has changed. The unit of update is therefore the **component**, not the whole subtree below the component that owns the state, and not a single DOM node. ## Walking the example 1. `count` changes. The parent's template read `count` (to pass it as `:value`), so the **parent's** render effect is scheduled and re-runs, returning a new vnode tree with three new `Stat` vnodes. 2. The renderer patches the parent's old tree against the new one and reaches the first `Stat`. Old and new are the same component type, so it calls `updateComponent`, which asks `shouldUpdateComponent(old, new)`. 3. For the first `Stat`, the compiler flagged `value` as a dynamic prop; `1 !== 0`, so the answer is yes. The renderer sets the instance's pending vnode and runs that child's render effect: its props are updated and its template renders. 4. For the second `Stat`, `value` is dynamic but `10 === 10`, so the answer is no. The renderer copies `el` to the new vnode and moves on — `Stat`'s render function does not run. 5. The third `Stat` has only static props, so there is nothing to compare and it is skipped the same way. Result: **the parent and the first child** re-render. Anything below the second and third children is untouched. Below the first child, the same props check decides each grandchild independently. ## What the child check looks at | Situation on the child vnode | Child re-renders? | |---|---| | A dynamic prop's value differs (`!==`) | Yes | | All props equal, no other flags | No | | A listener declared in the child's `emits` changed | No — declared emit listeners are ignored in the comparison | | Slot content depends on `v-if`/`v-for` in the parent (dynamic slots) | Yes | | A runtime directive or a `<Transition>` applies to the component vnode | Yes | | Hand-written render function passing children that are not marked stable | Yes | Note that the comparison is **shallow**: an object prop compares by reference. Replacing an array with an equal copy counts as a change; mutating an object in place does not. ## When the state lives somewhere else Move the value out of the parent — say, into a shared `reactive()` object that only the child reads — and the picture changes. The parent's render never read it, so the parent does not re-render at all; only the child's render effect re-runs. The same applies to an object prop mutated in place: `user.name = 'x'` re-renders the components whose renders read `user.name`, and the parent only if its own template read it too. Granularity follows **reads**, not the shape of the component tree. ## Why interviewers ask this The question separates candidates who carry a React mental model ("a parent re-render re-renders its children") from those who know Vue's: tracked reads per component plus a props check at each child boundary. It also sets up the practical advice owned elsewhere — keeping props stable so the check can say no. - Know that the parent re-renders only because **it** read the state. - Know that siblings are **visited** by the patch but **not re-rendered**. - Know the exceptions that force a child update even with equal props. - Know that a new object or array literal written inline in the parent template is a new reference on every parent render, so the child it is passed to fails the check each time. - Know that the check runs per child: one noisy child does not drag its siblings along. A strong answer names the unit of work — one render effect per component instance — before describing any optimization.

  • In Vue 3, if the parent mutates `user.name` on a reactive object passed as the `user` prop, which components re-render?
    The prop reference is unchanged, so the parent-side props check sees nothing new. Components re-render only if their own render read `user.name`: the child that displays it re-renders through its own effect, and the parent re-renders only if its template read `user.name` as well.
  • In Vue 3, when does a child re-render even though none of its props changed?
    When reactive state it read during its own render changes; when the slot content passed to it is dynamic (built under `v-if` or `v-for`); when a runtime directive or a transition is applied to its component vnode; or when a hand-written render function passes children that are not marked stable.
  • How does this compare with React's default behaviour?
    In React, re-rendering a parent calls every child component it renders again unless the child is wrapped in `memo`, which compares props shallowly. Vue performs a props comparison at every child boundary by default, and additionally re-renders any component directly from its own tracked reads, without involving the parent.

saying these in an interview costs you the question

  • Says all three children re-render because their parent re-rendered.
  • Believes only the changed child re-renders and the parent never does.
  • Thinks Vue deep-compares object props before deciding to update a child.
  • Assumes a child re-renders when an unrelated sibling's prop changes.
  • Claims mutating a nested field of an object prop re-renders the parent regardless.
open as a page

In Vue 3, how does the runtime-only build differ from the full build, and why do most apps ship the runtime-only one?

level: juniorimportance: should knowfreq 42%

basics

~20 s

The full build includes Vue's template compiler, so it can compile template strings in the browser; the runtime-only build does not. Apps with a build step precompile templates into render functions, so shipping the compiler would be wasted bytes and work.

open as a page

In Vue 3, what runtime hints does a compiled template give the renderer that a hand-written h() render function does not?

level: juniorimportance: should knowfreq 34%

basics

~20 s

A compiled template adds patch flags, cached static vnodes and blocks with a flat list of dynamic descendants, so updates touch only what can change. h() output has none of these, so Vue fully diffs its props and children on each update.

open as a page

In Vue 3, what arguments does h() take, and how does a Composition API component return a render function instead of a template?

level: juniorimportance: should knowfreq 38%

basics

~20 s

h(type, props?, children?) creates a vnode from a tag string or component, flat props with onXxx listeners, and children. setup() returns a function, not a vnode; that function re-runs on every render while setup runs once.

open as a page

In Vue 3, what is a vnode, and what is the difference between the renderer mounting it and patching it?

level: juniorimportance: should knowfreq 55%

basics

~20 s

A vnode is a plain JavaScript object describing a piece of UI: its type, props, children and key. Mounting creates real DOM from a new vnode tree; patching compares the new tree with the previous one and changes only what differs.

open as a page

When a Vue 3 template is written directly in a page's HTML, which browser parsing caveats apply and how do you work around each?

level: middleimportance: should knowfreq 32%

basics

~20 s

Vue compiles an in-DOM template only after the browser has parsed it, so names are lowercased, self-closing custom tags stay open and invalid children are hoisted. Use kebab-case, explicit closing tags and is="vue:component-name" on a valid native element.

open as a page

In Vue 3 compiled templates, what does a patch flag like 2 /* CLASS */ mean, and how does the renderer use it?

level: middleimportance: should knowfreq 36%

basics

~20 s

A patch flag is a bitmask the compiler adds to a dynamic vnode saying which parts can change: TEXT, CLASS, STYLE, PROPS and so on. When patching, the renderer checks those bits and updates only the flagged aspects, skipping static attributes entirely.

open as a page

In Vue 3, how would you write a heading component that renders <h1> to <h6> from a level prop using a render function?

level: middleimportance: should knowfreq 30%

basics

~20 s

Return a render function from setup() that calls h(h${props.level}, slots.default?.()). The tag is a plain string, so it follows the prop on every render, and calling the slot inside the render function keeps the heading text current.

open as a page

How does writing JSX or TSX in Vue 3 differ from React JSX in attributes, events, children and TypeScript setup?

level: middleimportance: should knowfreq 28%

basics

~20 s

Vue compiles JSX with its own transform into Vue vnodes: you write class and for, events are onXxx props, and a component's children are slot functions or an object of them. TSX needs jsx: preserve and, since 3.4, jsxImportSource: vue.

open as a page

When Vue 3's renderer patches a list of child vnodes, how does its keyed algorithm differ from the unkeyed one?

level: middleimportance: should knowfreq 45%

basics

~20 s

Unkeyed patching pairs old and new children by index, patches the shared length, then mounts or unmounts the tail. Keyed patching trims a matching prefix and suffix, matches the rest by key, and moves only nodes outside the longest increasing subsequence.

open as a page

A Vue 3 page imports createApp from 'vue' through a bundler and mounts a template-less root onto server-rendered markup, but the markup vanishes: what went wrong?

level: seniorimportance: should knowfreq 26%

basics

~20 s

The bundler's default 'vue' entry is the runtime-only build. mount() copies the container's HTML into the root's template and clears the container, but no compiler is registered, so nothing renders and a dev-only warning names the full build to use.

open as a page

A Vue 3 child's setup and onMounted run again on every parent re-render, wiping its state; what makes the renderer replace it instead of patching it?

level: seniorimportance: should knowfreq 32%

basics

~20 s

Vue 3 patches a vnode only when old and new share the same type and key. A component object created during render, or a key that changes on every render, fails that test, so the renderer unmounts and remounts the child.

open as a page

What are Vue 3's compile-time feature flags such as __VUE_OPTIONS_API__, which builds read them, and what happens if you never define them?

level: middleimportance: nice to knowfreq 18%

basics

~20 s

They are global constants the esm-bundler builds expect the bundler to replace so unused code can be tree-shaken: VUE_OPTIONS_API (default true), VUE_PROD_DEVTOOLS and VUE_PROD_HYDRATION_MISMATCH_DETAILS (both false). Left undefined, Vue applies the defaults and warns in development.

open as a page

Reading the compiled output of a Vue 3.5 template that is mostly static markup, what do _cache[0], -1 /* CACHED */ and createStaticVNode tell you?

level: middleimportance: nice to knowfreq 22%

basics

~20 s

Static subtrees are created once and stored in the instance's _cache, marked -1 CACHED, so later renders reuse them and the renderer skips them. Long static runs are stringified into one createStaticVNode HTML string mounted via innerHTML.

open as a page

In a Vue 3 render function, how do you pass default, named and scoped slots to a child component, and why must they be functions?

level: middleimportance: nice to knowfreq 24%

basics

~20 s

Pass a function for the default slot, or null props plus an object of slot functions for named ones. Scoped slots are functions with a parameter. Functions let the child call them lazily, so the child tracks their dependencies.

open as a page

In Vue 3's renderer, what is a block, what does its dynamicChildren array hold, and why do v-if and v-for start new blocks?

level: seniorimportance: nice to knowfreq 20%

basics

~20 s

A block is a template region with stable structure; its dynamicChildren array is a flat list of every descendant with a patch flag plus components. Updates patch that list pairwise, so v-if and v-for, which change structure, get their own blocks.

open as a page

A Vue 3 render function uses h('ButtonCounter') and h('input', { 'v-focus': true }) for a registered component and directive, but neither works: why, and how do you fix it?

level: seniorimportance: nice to knowfreq 16%

basics

~10 s

A string passed to h() is always a native tag, and v-focus in props is just an attribute. Use resolveComponent('ButtonCounter') or an import for the component, and withDirectives(h('input'), [[focusDirective]]) with resolveDirective('focus') for the directive.

open as a page