[{"data":1,"prerenderedAt":734},["ShallowReactive",2],{"blog-\u002Fblog\u002Flook-it-up-once":3},{"id":4,"title":5,"body":6,"canonical":724,"date":725,"description":726,"draft":727,"duration":724,"extension":728,"lang":724,"meta":729,"navigation":386,"path":730,"place":724,"placeLink":724,"readingTime":724,"seo":731,"stem":732,"__hash__":733},"blog\u002Fblog\u002Flook-it-up-once.md","Look it up once",{"type":7,"value":8,"toc":712},"minimark",[9,19,31,48,58,93,114,117,130,135,142,151,154,164,168,171,174,309,316,325,329,332,510,528,531,539,547,550,556,565,569,572,597,610,613,616,620,623,645,651,655,658,676,685,689,696,699,708],[10,11,13,14,18],"h3",{"id":12},"why-calling-css-on-every-render-stays-cheap","Why calling ",[15,16,17],"code",{},"css()"," on every render stays cheap.",[20,21,22],"p",{},[23,24],"img",{"alt":25,"fetchPriority":26,"loading":27,"src":28,"width":29,"height":30},"Look it up once, on a cream background with yellow and purple glows, surrounded by the Panda logo and stickers reading memo(css), WeakMap and hash, trie, string.","high","eager","\u002Fimages\u002Fblog\u002Flook-it-up-once\u002Fcover.svg",1200,630,[20,32,33],{},[34,35,36,37,42,43,47],"em",{},"Panda internals, part 3. Part 1: ",[38,39,41],"a",{"href":40},"\u002Fblog\u002Fbuild-once-at-build-time","Build once, at build time",". Part 2: ",[38,44,46],{"href":45},"\u002Fblog\u002Fevery-style-is-an-atom","Every style is an atom",".",[20,49,50,51,53,54,57],{},"In Panda, the CSS is already written at build time. What ",[15,52,17],{}," does in the browser is hand back class names. This call returns ",[15,55,56],{},"'c_red.300 p_4'",":",[59,60,65],"pre",{"className":61,"code":62,"language":63,"meta":64,"style":64},"language-ts shiki shiki-themes github-light github-dark","css({ color: 'red.300', padding: '4' })\n","ts","",[15,66,67],{"__ignoreMap":64},[68,69,72,76,80,84,87,90],"span",{"class":70,"line":71},"line",1,[68,73,75],{"class":74},"sScJk","css",[68,77,79],{"class":78},"sVt8B","({ color: ",[68,81,83],{"class":82},"sZZnC","'red.300'",[68,85,86],{"class":78},", padding: ",[68,88,89],{"class":82},"'4'",[68,91,92],{"class":78}," })\n",[20,94,95,96,98,99,102,103,106,107,110,111,47],{},"No CSS gets created there. But ",[15,97,17],{}," runs a lot. Every component, every render, and on the server for every request. So Panda wraps it in a small cache, ",[15,100,101],{},"memo()",". The same goes for ",[15,104,105],{},"cva",", ",[15,108,109],{},"sva",", recipes and patterns in the generated ",[15,112,113],{},"styled-system",[20,115,116],{},"A cache needs a key, and the key has to be cheap to build. Panda picks one of three, per call:",[118,119,120,124,127],"ol",{},[121,122,123],"li",{},"Flat styles get a hash of their values.",[121,125,126],{},"Composed styles get looked up by the objects inside them.",[121,128,129],{},"Everything else gets a string.",[131,132,134],"h2",{"id":133},"building-the-key-was-the-slow-part","Building the key was the slow part",[20,136,137,138,141],{},"The first version of this cache used ",[15,139,140],{},"JSON.stringify",". Turn the arguments into a string, use the string as the key.",[20,143,144,145,147,148,150],{},"It's correct, but it runs on every call, hits and misses alike. To find out the answer was already sitting in the cache, ",[15,146,101],{}," first had to serialize the whole style object. In a page full of variant buttons, that stringify was the single biggest cost in the whole ",[15,149,17],{}," path.",[20,152,153],{},"That's why the memo in v2 picks its key based on what the arguments look like.",[20,155,156,157,159,160,163],{},"There's also a check before any of the three. When ",[15,158,17],{}," gets a single style object, it first looks that exact object up in a ",[15,161,162],{},"WeakMap",". Pass the same object again, like one defined once at module level, and that's the whole lookup. An object written inline is a new object on every render, though, so it misses and moves on.",[131,165,167],{"id":166},"_1-flat-styles-hash-the-values","1. Flat styles: hash the values",[20,169,170],{},"The call at the top of this post is the common case the hash is built for. One object, no nesting, plain strings and numbers as values.",[20,172,173],{},"For those, Panda skips the string entirely. It walks the object once and folds every key and value into a number:",[59,175,177],{"className":61,"code":176,"language":63,"meta":64,"style":64},"let h = 5381\nfor (const key in style) {\n  const text = key + String(style[key])\n  for (const ch of text) {\n    h = (h * 33) ^ ch.charCodeAt(0)\n  }\n}\n",[15,178,179,195,216,240,259,297,303],{"__ignoreMap":64},[68,180,181,185,188,191],{"class":70,"line":71},[68,182,184],{"class":183},"szBVR","let",[68,186,187],{"class":78}," h ",[68,189,190],{"class":183},"=",[68,192,194],{"class":193},"sj4cs"," 5381\n",[68,196,198,201,204,207,210,213],{"class":70,"line":197},2,[68,199,200],{"class":183},"for",[68,202,203],{"class":78}," (",[68,205,206],{"class":183},"const",[68,208,209],{"class":193}," key",[68,211,212],{"class":183}," in",[68,214,215],{"class":78}," style) {\n",[68,217,219,222,225,228,231,234,237],{"class":70,"line":218},3,[68,220,221],{"class":183},"  const",[68,223,224],{"class":193}," text",[68,226,227],{"class":183}," =",[68,229,230],{"class":78}," key ",[68,232,233],{"class":183},"+",[68,235,236],{"class":74}," String",[68,238,239],{"class":78},"(style[key])\n",[68,241,243,246,248,250,253,256],{"class":70,"line":242},4,[68,244,245],{"class":183},"  for",[68,247,203],{"class":78},[68,249,206],{"class":183},[68,251,252],{"class":193}," ch",[68,254,255],{"class":183}," of",[68,257,258],{"class":78}," text) {\n",[68,260,262,265,267,270,273,276,279,282,285,288,291,294],{"class":70,"line":261},5,[68,263,264],{"class":78},"    h ",[68,266,190],{"class":183},[68,268,269],{"class":78}," (h ",[68,271,272],{"class":183},"*",[68,274,275],{"class":193}," 33",[68,277,278],{"class":78},") ",[68,280,281],{"class":183},"^",[68,283,284],{"class":78}," ch.",[68,286,287],{"class":74},"charCodeAt",[68,289,290],{"class":78},"(",[68,292,293],{"class":193},"0",[68,295,296],{"class":78},")\n",[68,298,300],{"class":70,"line":299},6,[68,301,302],{"class":78},"  }\n",[68,304,306],{"class":70,"line":305},7,[68,307,308],{"class":78},"}\n",[20,310,311,312,315],{},"That's simplified. If it hits a nested object halfway through, it gives up and returns ",[15,313,314],{},"null",", and the call moves on to another path.",[20,317,318,319,47],{},"A hash can collide, so a match on the number isn't trusted on its own. Panda keeps a few entries per hash and compares the actual keys and values before returning anything. On a variant-heavy button benchmark, that alone gave ",[38,320,324],{"href":321,"rel":322},"https:\u002F\u002Fgithub.com\u002Fchakra-ui\u002Fpanda\u002Fcommit\u002F6e3c1600dc197250ae61ccda76fb79cff6cba53f",[323],"nofollow","30 to 40 percent more SSR throughput",[131,326,328],{"id":327},"_2-composed-styles-follow-the-objects","2. Composed styles: follow the objects",[20,330,331],{},"You build a component on top of a component, and forward styles down:",[59,333,337],{"className":334,"code":335,"language":336,"meta":64,"style":64},"language-tsx shiki shiki-themes github-light github-dark","const base = css.raw({ px: '4' })\nconst danger = css.raw({ bg: 'red.500' })\n\nfunction Button({ css: extra = [] }) {\n  const cls = css([base, ...extra])\n  return \u003Cbutton className={cls} \u002F>\n}\n\nfunction DangerButton({ css: extra = [] }) {\n  return \u003CButton css={[danger, ...extra]} \u002F>\n}\n","tsx",[15,338,339,361,382,388,413,434,454,458,463,483,505],{"__ignoreMap":64},[68,340,341,343,346,348,351,354,357,359],{"class":70,"line":71},[68,342,206],{"class":183},[68,344,345],{"class":193}," base",[68,347,227],{"class":183},[68,349,350],{"class":78}," css.",[68,352,353],{"class":74},"raw",[68,355,356],{"class":78},"({ px: ",[68,358,89],{"class":82},[68,360,92],{"class":78},[68,362,363,365,368,370,372,374,377,380],{"class":70,"line":197},[68,364,206],{"class":183},[68,366,367],{"class":193}," danger",[68,369,227],{"class":183},[68,371,350],{"class":78},[68,373,353],{"class":74},[68,375,376],{"class":78},"({ bg: ",[68,378,379],{"class":82},"'red.500'",[68,381,92],{"class":78},[68,383,384],{"class":70,"line":218},[68,385,387],{"emptyLinePlaceholder":386},true,"\n",[68,389,390,393,396,399,402,405,408,410],{"class":70,"line":242},[68,391,392],{"class":183},"function",[68,394,395],{"class":74}," Button",[68,397,398],{"class":78},"({ ",[68,400,75],{"class":401},"s4XuR",[68,403,404],{"class":78},": ",[68,406,407],{"class":401},"extra",[68,409,227],{"class":183},[68,411,412],{"class":78}," [] }) {\n",[68,414,415,417,420,422,425,428,431],{"class":70,"line":261},[68,416,221],{"class":183},[68,418,419],{"class":193}," cls",[68,421,227],{"class":183},[68,423,424],{"class":74}," css",[68,426,427],{"class":78},"([base, ",[68,429,430],{"class":183},"...",[68,432,433],{"class":78},"extra])\n",[68,435,436,439,442,446,449,451],{"class":70,"line":299},[68,437,438],{"class":183},"  return",[68,440,441],{"class":78}," \u003C",[68,443,445],{"class":444},"s9eBZ","button",[68,447,448],{"class":74}," className",[68,450,190],{"class":183},[68,452,453],{"class":78},"{cls} \u002F>\n",[68,455,456],{"class":70,"line":305},[68,457,308],{"class":78},[68,459,461],{"class":70,"line":460},8,[68,462,387],{"emptyLinePlaceholder":386},[68,464,466,468,471,473,475,477,479,481],{"class":70,"line":465},9,[68,467,392],{"class":183},[68,469,470],{"class":74}," DangerButton",[68,472,398],{"class":78},[68,474,75],{"class":401},[68,476,404],{"class":78},[68,478,407],{"class":401},[68,480,227],{"class":183},[68,482,412],{"class":78},[68,484,486,488,490,493,495,497,500,502],{"class":70,"line":485},10,[68,487,438],{"class":183},[68,489,441],{"class":78},[68,491,492],{"class":193},"Button",[68,494,424],{"class":74},[68,496,190],{"class":183},[68,498,499],{"class":78},"{[danger, ",[68,501,430],{"class":183},[68,503,504],{"class":78},"extra]} \u002F>\n",[68,506,508],{"class":70,"line":507},11,[68,509,308],{"class":78},[20,511,512,513,516,517,520,521,516,524,527],{},"Every render builds new arrays, ",[15,514,515],{},"[danger, ...extra]"," and ",[15,518,519],{},"[base, ...extra]",". But ",[15,522,523],{},"base",[15,525,526],{},"danger"," are the same objects every time, created once when the module loaded.",[20,529,530],{},"The hash can't help here, because the arguments contain arrays. And stringify serializes the whole composition again, every render, for every element.",[20,532,533,534,538],{},"So when the arguments contain an array, Panda keys the call on ",[535,536,537],"strong",{},"object identity"," instead of value. It walks a small tree, a trie, one step per item in the arguments:",[59,540,545],{"className":541,"code":543,"language":544},[542],"language-text","root\n└─ base\n   └─ danger → 'px_4 bg_red.500'\n","text",[15,546,543],{"__ignoreMap":64},[20,548,549],{},"The same objects in the same order always reach the same leaf.",[20,551,552,553,555],{},"Each step that holds an object is a ",[15,554,162],{},", so a style object that only lived for one render isn't kept alive by the cache.",[20,557,558,559,564],{},"On a three-level chain, that ",[38,560,563],{"href":561,"rel":562},"https:\u002F\u002Fgithub.com\u002Fchakra-ui\u002Fpanda\u002Fcommit\u002F25137db545ce9129462662483390b49f82d85ba8",[323],"took a composed call from 1.52ms to 0.85ms",". On a six-level chain, 2.11ms to 1.28ms.",[10,566,568],{"id":567},"only-objects-it-has-seen-before","Only objects it has seen before",[20,570,571],{},"Keying on identity works for the chain above, but not for this:",[59,573,575],{"className":334,"code":574,"language":336,"meta":64,"style":64},"\u003CButton css={[{ mt: '2' }]} \u002F>\n",[15,576,577],{"__ignoreMap":64},[68,578,579,582,584,586,588,591,594],{"class":70,"line":71},[68,580,581],{"class":78},"\u003C",[68,583,492],{"class":193},[68,585,424],{"class":74},[68,587,190],{"class":183},[68,589,590],{"class":78},"{[{ mt: ",[68,592,593],{"class":82},"'2'",[68,595,596],{"class":78}," }]} \u002F>\n",[20,598,599,600,603,604,609],{},"That ",[15,601,602],{},"{ mt: '2' }"," is a fresh object on every render. Its identity never repeats, so the trie would never hit. Worse, every render would add new branches that nothing ever reads again. Keying every composed call on identity made this shape ",[38,605,608],{"href":606,"rel":607},"https:\u002F\u002Fgithub.com\u002Fchakra-ui\u002Fpanda\u002Fcommit\u002F172c52fb5326e84c3ab7e9aea2b718b1c0610ed2",[323],"about 7x slower"," than just serializing it.",[20,611,612],{},"So a call only gets a place in the trie once the objects in it have been seen before. Until then, it goes down the value path like any other call. Reading the trie allocates nothing, so a miss is cheap. And the \"have I seen these objects?\" bookkeeping only runs on every fourth miss, not every miss.",[20,614,615],{},"That's enough for a repeating chain to land in the trie within a few calls, while inline styles barely pay anything for being checked. The inline shape ended up faster than it was before identity keys existed.",[131,617,619],{"id":618},"_3-everything-else-stringify","3. Everything else: stringify",[20,621,622],{},"Responsive values, nested conditions, anything the hash bails on and that isn't a composed array:",[59,624,626],{"className":61,"code":625,"language":63,"meta":64,"style":64},"css({ padding: { base: '2', md: '4' } })\n",[15,627,628],{"__ignoreMap":64},[68,629,630,632,635,637,640,642],{"class":70,"line":71},[68,631,75],{"class":74},[68,633,634],{"class":78},"({ padding: { base: ",[68,636,593],{"class":82},[68,638,639],{"class":78},", md: ",[68,641,89],{"class":82},[68,643,644],{"class":78}," } })\n",[20,646,647,648,650],{},"These still use ",[15,649,140],{},". V8's stringify is native code, and it beats walking a nested object in JavaScript. For shapes that don't fit the first two paths, it's the fastest key available.",[131,652,654],{"id":653},"how-big-the-cache-can-get","How big the cache can get",[20,656,657],{},"Each part of the cache has a limit:",[659,660,661,664,667,670],"ul",{},[121,662,663],{},"The last call is checked first, because re-rendering with the same arguments is common.",[121,665,666],{},"Each hash keeps up to eight entries. Older ones for the same hash drop off.",[121,668,669],{},"Each cache keeps up to 500 entries. Past that, the oldest goes.",[121,671,672,673,675],{},"Identity lookups use ",[15,674,162],{},"s, so they don't hold on to objects your code has let go of.",[20,677,678,679,684],{},"Put together, on dense server-rendered pages, the ",[38,680,683],{"href":681,"rel":682},"https:\u002F\u002Fpanda-css.com\u002Fblog\u002Fpanda-css-v2",[323],"v2 benchmarks"," have repeated inline styles about 3x faster than the unmemoized path, a three-level wrapper chain about 4x, a six-level chain about 3x, and repeated flat objects 30 to 40 percent faster.",[131,686,688],{"id":687},"calls-that-never-run","Calls that never run",[20,690,691,692,695],{},"Part 1 covered source transforms. With ",[15,693,694],{},"transform: true",", the bundler replaces calls Panda can fully work out at build time with the plain class string. Those never reach the memo at all, because they never run.",[20,697,698],{},"What's left at runtime is what couldn't be resolved ahead of time, like styles built from props or state. Those are the calls the memo is for.",[20,700,701],{},[34,702,703,704,516,706,47],{},"Next in the series: how Panda works out your style values without ever running your code. If you're new here, start with ",[38,705,41],{"href":40},[38,707,46],{"href":45},[709,710,711],"style",{},"html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}html pre.shiki code .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}",{"title":64,"searchDepth":197,"depth":197,"links":713},[714,716,717,718,721,722,723],{"id":12,"depth":218,"text":715},"Why calling css() on every render stays cheap.",{"id":133,"depth":197,"text":134},{"id":166,"depth":197,"text":167},{"id":327,"depth":197,"text":328,"children":719},[720],{"id":567,"depth":218,"text":568},{"id":618,"depth":197,"text":619},{"id":653,"depth":197,"text":654},{"id":687,"depth":197,"text":688},null,"2026-10-20","Why calling css() on every render stays cheap. The three ways Panda's runtime memo builds a cache key, and why it waits before trusting an object.",false,"md",{},"\u002Fblog\u002Flook-it-up-once",{"title":5,"description":726},"blog\u002Flook-it-up-once","82MloW_ncqJxAiZ3cv2KDHLFaWjGLqTgrNmUm41x3uY",1792585585672]