Skip to the solution
HardFrontend11 min read

What are v-once and v-memo?

1.2k views

The problem

Question presented to candidate: "What do v-once and v-memo do? Walk me through how the compiled render function differs for each, and tell me when v-memo is worth using and how it can bite you."

What a strong answer should cover:

  • v-once renders an element or component once; later renders reuse the cached VNode, so it never updates even if its data changes.
  • v-memo="[a, b]" caches a subtree and reuses it while every value in the array is unchanged; v-memo="[]" behaves like v-once.
  • Compiled output: v-once becomes a _cache[i] || (...) expression with setBlockTracking(-1); v-memo becomes _withMemo(deps, render, _cache, i); on a v-for it becomes an inline _isMemoSame check per item, also comparing the key.
  • A cache hit skips creating the VNodes and diffing them, not just DOM updates.
  • Main use: large v-for lists where only a few rows change (such as the selected row); the dependency array must include everything that should cause an update.
  • Pitfalls: stale UI from missing dependencies, v-memo placed inside a v-for instead of on it, and both directives being ignored during SSR.

Clarifying questions expected:

  • "How large is the list, and what changes between renders?"
  • "Has profiling shown the render function itself as the bottleneck?"

Code / implementation expected: Yes β€” compiled output for each directive, or a list with v-memo keyed on selection.

  • v-memo
  • v-once

Try it yourself

Sketch your approach before you read the solution. That is what the interview tests.

Stuck? Get a hint.

A short nudge toward the approach. It will not give the answer away.

Solution

The solution is hidden so you can try first

11 min read Β· 10 sections

Read the code

Vuev-once header and a v-memo list, with render counters
<script setup>
import { ref } from "vue";

const rows = ref(
  Array.from({ length: 5 }, (_, i) => ({ id: i + 1, label: "Row " + (i + 1) }))
);
const selected = ref(1);
const clicks = ref(0);

// Plain (non-reactive) counters: how many times each row body was re-rendered.
const renders = { plain: 0, memo: 0 };
const body = (kind, row) => {
  renders[kind]++;
  return row.label;
};
</script>

<template>
  <h4 v-once>Started with row {{ selected }} selected (v-once: never updates)</h4>
  <h4>Now selected: row {{ selected }}</h4>

  <button id="next" @click="selected = (selected % rows.length) + 1">select next</button>
  <button id="noise" @click="clicks++">unrelated update ({{ clicks }})</button>

  <div class="cols">
    <ul>
      <li v-for="row in rows" :key="row.id" :class="{ on: row.id === selected }">
        {{ body("plain", row) }}{{ row.id === selected ? " *" : "" }}
      </li>
    </ul>
    <ul>
      <li
        v-for="row in rows"
        :key="row.id"
        v-memo="[row.id === selected]"
        :class="{ on: row.id === selected }"
      >
        {{ body("memo", row) }}{{ row.id === selected ? " *" : "" }}
      </li>
    </ul>
  </div>

  <p id="stats">row bodies rendered so far: plain {{ renders.plain }}, v-memo {{ renders.memo }}</p>
</template>
Print the compiled render functions with @vue/compiler-dom
const { compile } = require("@vue/compiler-dom");
const show = (label, tpl, opts = {}) => {
  const { code } = compile(tpl, { mode: "module", hoistStatic: true, ...opts });
  console.log("=== " + label + "\n" + code.replace(/^import .*\n/gm, "").trim() + "\n");
};
show("plain", `<p>{{ msg }}</p>`);
show("v-once", `<p v-once>{{ msg }}</p>`);
show("v-memo", `<p v-memo="[msg]">{{ msg }} {{ other }}</p>`);
show("v-memo on v-for", `<div v-for="item in list" :key="item.id" v-memo="[item.id === selected]">{{ item.label }}</div>`);

Discussion

Other approaches, follow-ups and how it went in real interviews.

Sign in to join the discussion.

No comments yet. Share how you would answer it.

Question 72 of 92 in Vue.js

Keep your momentum going.

Mark this question solved to track your progress, then take the next one while it is fresh.

All Vue.js questions
Up nextHard

What is Vapor mode in Vue 3.5+ and how does it differ from the virtual DOM?

Continue