Patch Flags
What are Patch Flags?
Patch Flags are optimization hints generated by the compiler. By attaching flags to VNodes, the runtime's diffing algorithm can skip unnecessary checks and improve performance.

When humans write templates, they understand "this part is dynamic" and "this part is static", but traditional Virtual DOM doesn't know this. By having the compiler convey this information to the runtime, unnecessary comparisons can be eliminated!
How the Optimization Works
In normal Virtual DOM diffing, all properties and child elements need to be compared. However, the compiler knows "which parts are dynamic" at the template analysis stage. By embedding this information as Patch Flags in VNodes, the runtime can check only the parts that might change.
PatchFlags Definition
export const enum PatchFlags {
/**
* Element with dynamic textContent
*/
TEXT = 1,
/**
* Element with dynamic class binding
*/
CLASS = 1 << 1, // 2
/**
* Element with dynamic style
*/
STYLE = 1 << 2, // 4
/**
* Element with dynamic props other than class/style
*/
PROPS = 1 << 3, // 8
/**
* Element with props that have dynamic keys
*/
FULL_PROPS = 1 << 4, // 16
/**
* Props processing needed during hydration
*/
NEED_HYDRATION = 1 << 5, // 32
/**
* Fragment with children whose order doesn't change
*/
STABLE_FRAGMENT = 1 << 6, // 64
/**
* Fragment with keyed children
*/
KEYED_FRAGMENT = 1 << 7, // 128
/**
* Fragment with unkeyed children
*/
UNKEYED_FRAGMENT = 1 << 8, // 256
/**
* Patch needed for non-props (ref, directives, etc.)
*/
NEED_PATCH = 1 << 9, // 512
/**
* Component with dynamic slots
*/
DYNAMIC_SLOTS = 1 << 10, // 1024
/**
* For development: Fragment with comment at root
*/
DEV_ROOT_FRAGMENT = 1 << 11, // 2048
// Special flags (negative integers)
/**
* Cached static VNode
*/
CACHED = -1,
/**
* Hint to exit optimized mode
*/
BAIL = -2,
}Combining with Bitwise Operations
Patch Flags are designed as bit flags, allowing multiple flags to be combined.
// Combining flags
const flag = PatchFlags.TEXT | PatchFlags.CLASS; // 3 (0b11)
// Checking flags
if (flag & PatchFlags.TEXT) {
// TEXT flag is set
}
if (flag & PatchFlags.CLASS) {
// CLASS flag is set
}
1 << 1 is 2, 1 << 2 is 4... just shifting bits creates independent flags. Combine with | (OR), check with & (AND). Simple but super efficient!
Examples of Generation from Templates
Dynamic Text
<template>
<p>{{ message }}</p>
</template>Generated code:
// patchFlag = 1 (TEXT)
createVNode("p", null, toDisplayString(message), 1 /* TEXT */)Dynamic Class
<template>
<div :class="dynamicClass">Content</div>
</template>Generated code:
// patchFlag = 2 (CLASS)
createVNode("div", { class: dynamicClass }, "Content", 2 /* CLASS */)Multiple Dynamic Properties
<template>
<div :class="cls" :style="styles">{{ text }}</div>
</template>Generated code:
// patchFlag = 7 (TEXT | CLASS | STYLE)
createVNode("div",
{ class: cls, style: styles },
toDisplayString(text),
7 /* TEXT, CLASS, STYLE */
)Dynamic Props
<template>
<input :value="inputValue" :disabled="isDisabled">
</template>Generated code:
// patchFlag = 8 (PROPS)
// dynamicProps explicitly specifies props that may change
createVNode("input",
{ value: inputValue, disabled: isDisabled },
null,
8 /* PROPS */,
["value", "disabled"]
)Usage in Runtime
Optimization in patchElement
function patchElement(n1: VNode, n2: VNode) {
const el = n2.el = n1.el;
const { patchFlag, dynamicProps } = n2;
if (patchFlag > 0) {
// Optimized path: update only necessary parts based on flags
if (patchFlag & PatchFlags.CLASS) {
// Update only class
if (n1.props?.class !== n2.props?.class) {
hostSetClass(el, n2.props?.class);
}
}
if (patchFlag & PatchFlags.STYLE) {
// Update only style
hostPatchStyle(el, n1.props?.style, n2.props?.style);
}
if (patchFlag & PatchFlags.PROPS) {
// Update only specified props
for (const key of dynamicProps!) {
const prev = n1.props?.[key];
const next = n2.props?.[key];
if (prev !== next) {
hostPatchProp(el, key, prev, next);
}
}
}
if (patchFlag & PatchFlags.TEXT) {
// Update only text content
if (n1.children !== n2.children) {
hostSetElementText(el, n2.children as string);
}
}
} else if (patchFlag === PatchFlags.FULL_PROPS) {
// Check all props
patchProps(el, n1.props, n2.props);
} else {
// No flags: full diff
patchProps(el, n1.props, n2.props);
patchChildren(n1, n2, el);
}
}Fragment Optimization
function patchFragment(n1: VNode, n2: VNode) {
const { patchFlag } = n2;
if (patchFlag & PatchFlags.STABLE_FRAGMENT) {
// Children order doesn't change: simple update
patchBlockChildren(n1.children, n2.children);
} else if (patchFlag & PatchFlags.KEYED_FRAGMENT) {
// Keyed children: key-based diff
patchKeyedChildren(n1.children, n2.children);
} else {
// Unkeyed: full diff
patchUnkeyedChildren(n1.children, n2.children);
}
}Special Flags
CACHED (-1)
Indicates that a static VNode is cached.
const _hoisted_1 = createVNode("div", null, "Static", -1 /* CACHED */);Cached VNodes can skip diffing.
BAIL (-2)
A hint to exit optimized mode. Used when the compiler's optimizations cannot be applied, such as when the user is using hand-written render functions.
dynamicProps
The dynamicProps array, used together with patchFlag, explicitly indicates which props are dynamic.
// Dynamic props are value and disabled
createVNode("input",
{ type: "text", value: val, disabled: isDisabled },
null,
8 /* PROPS */,
["value", "disabled"] // dynamicProps
)This allows skipping comparison for type since it's static, and only checking value and disabled.
Integration with Block Tree
Patch Flags work in conjunction with Block Tree optimization. Blocks have a dynamicChildren array that tracks only dynamic child nodes.
const block = openBlock();
const vnode = createBlock("div", null, [
createVNode("p", null, "static"), // not included in dynamicChildren
createVNode("p", null, toDisplayString(msg), 1 /* TEXT */) // included
]);
// block.dynamicChildren = [only the dynamic p]When updating a Block, only dynamicChildren needs to be traversed, allowing comparison of static child nodes to be skipped.
Effect of Optimization
Before (without flags)
Compare all props: O(n)
Compare all children: O(m)
Total: O(n + m)After (with flags)
Compare only dynamic props: O(k) where k << n
Compare only dynamic children: O(l) where l << m
Total: O(k + l)When most of the template is static, this optimization has a significant effect.
Summary
The Patch Flags implementation consists of the following elements:
- Bit flags: Efficiently represent multiple dynamic elements
- Compiler integration: Automatically generated during template analysis
- Runtime optimization: Skip unnecessary comparisons based on flags
- dynamicProps: Explicitly track dynamic props
- Block Tree integration: Efficiently update only dynamic child nodes
Patch Flags are an important optimization technique that significantly improves Vue 3's Virtual DOM performance. By having the compiler and runtime work together, the advantages of template-based frameworks are maximized.

This technology was born from the idea "If we can analyze templates, we can also provide optimization hints." Please experience the strength of template compilers that JSX doesn't have!
Source code up to this point: chibivue (GitHub)
