LUCENTCOMMERCEGET A FREE STORE AUDITFREE AUDIT

LIQUID · PERFORMANCE · TECHNICAL · 6 MAY 2025 · 7 MIN READ

Liquid performance: the loops that cost you a second

Nested loops, metafields inside iterations, and filters recomputed 200 times. The patterns that make a theme slow on the server — and why that is usually not your worst problem.

A Liquid template open beside the section it renders

Four patterns account for most of it: loops nested across products, variants or options, which Shopify’s own guidance describes as growing quadratically with catalogue size; metafield access inside a loop; filters and assign statements recomputed on every iteration instead of once before it; and deep render nesting inside a loop. All four are invisible in a browser waterfall because they happen before the first byte leaves Shopify. Fix them with the Theme Inspector, not by guessing — and check first that Liquid is actually your problem, because it usually is not.

IN SHORT

  • Liquid renders on Shopify’s servers, so slow Liquid shows up as server response time, not as a slow-loading image or script.
  • Shopify’s Liquid documentation states that `for` loops are limited to 50 iterations per page, which is why the `paginate` tag exists.
  • `paginate` takes a page size between 1 and 250 and cannot page beyond the 25,000th item in an array.
  • The `all_products` object has a limit of 20 unique handles per page; use a collection if you need more.
  • Shopify’s performance guidance names the expensive patterns explicitly: nested loops, metafield access inside loops, repeated filter and `assign` calls, and repeated `block.settings` lookups.
  • Use the Shopify Theme Inspector for Chrome to find the slowest Liquid lines before changing any of them.

First, work out whether Liquid is the problem at all

Liquid runs on Shopify’s servers. Every millisecond it costs lands in the wait before the browser receives any HTML — server response time — and none of it appears as a slow request in a network waterfall. So the symptoms are specific: a page that sits blank and then arrives all at once, a collection page noticeably slower than a product page, a template that got slower when you added a feature nobody can point to.

The symptoms Liquid does not cause are the ones most people are actually complaining about. A hero image that takes three seconds to paint is an image problem. A page that renders and then jumps is a layout shift problem. A product page that is interactive only after a delay is JavaScript — usually app scripts. We have audited plenty of “slow themes” where the Liquid was fine and eleven third-party scripts were not.

This matters because the two cost different amounts to fix. Removing an app costs a conversation. Rewriting a collection template costs a sprint. Do the measurement before you pick.

The 50-iteration limit, and what it tells you

Shopify’s documentation is direct about this: “for loops are limited to 50 iterations per page,” and that limit is the reason the paginate tag exists. paginate takes a page size “between 1 and 250,” and it “allows the user to paginate to the 25,000th item in the array and no further.”

Read the limit as a design hint rather than a nuisance. A single flat loop over 50 things is cheap. The cost almost never comes from one loop being long — it comes from what is happening *inside* it, and from how many loops are stacked on top of each other. A 50-product loop that does four expensive things per product is 200 expensive things, and the template does not look any different from one that does none.

The quadratic patterns

Shopify’s performance guidance names this one outright: “Loops nested across products, variants, or options grow quadratically with catalog size,” and it tells theme developers to “eliminate O(n²) patterns, including nested loops and metafield access inside loops.”

In practice, this is what it looks like on a real store. A collection template loops products. Inside, it loops variants to work out whether anything is on sale, or to collect the colours for a swatch row. Inside that, it reads options. On a store with small variant counts the page feels fine, which is exactly why the pattern ships. Then the catalogue grows, or someone adds a third size axis, and a template nobody has touched in a year gets slow with no commit to blame.

Metafield access inside the loop is the same shape and often worse, because each read is data the server has to go and get. Shopify’s advice is to move it outside the loop. Where the value genuinely varies per product, the fix is usually to pre-compute once — a single metafield read on the collection, or a value written at publish time — rather than to ask per row.

Iterating every variant has its own note in the guidance: it “forces expensive database queries,” and the advice is to load only what the initial render needs. A product with 120 variants does not need all 120 serialised into the first response so that a picker can use four of them.

The things that look free

These are the ones that survive code review, because each individual line is obviously cheap. Multiplied by the loop, they are not.

  • A filter called repeatedly with identical arguments. Shopify’s guidance is to run it once and cache the result. The same money or image_url call with the same input, 50 times, is 49 wasted.
  • `assign` inside the loop that does not depend on the loop. Hoist it above. This is the single most common one we find, and the cheapest to fix.
  • Repeated `block.settings` access, which Shopify describes as creating “redundant object lookups.” Assign the settings you need once at the top of the block and read the variable after that.
  • Conditional logic inside loops, which “multiplies overhead per iteration.” Where the condition is constant for the whole loop, put the if around the loop rather than inside it.
  • Deeply nested `render` calls, which add overhead — “especially within loops.” A snippet that renders a snippet that renders a snippet, once per product, is a structure to flatten.
  • Over-fetching products, which the guidance describes as causing “unnecessary database strain.” Ask for what the template renders, not for the whole collection because it was easier.

The limit nobody reads until it bites

all_products is convenient and bounded: Shopify states that “the all_products object has a limit of 20 unique handles per page,” and recommends using a collection if you need more than that on a single template. It exists for the handful of specific products a template genuinely needs to name — a featured pair, an upsell, a bundle component.

What it is not is a product API. Homepages that reach for all_products to assemble a merchandising grid, and “you may also like” sections built by looking up handles from a metafield list, are both one product away from silently hitting the limit. A collection — even an automated one that exists purely to feed a section — is the right tool, and it comes with paginate when the list grows.

Measure it properly, in this order

Shopify’s own recommendation is to identify the slowest Liquid lines with the Shopify Theme Inspector for Chrome before optimising anything. Do that first. It attributes server render time to specific lines, and it routinely disagrees with the developer’s intuition about which section is the expensive one.

Then run Theme Check, which catches performance violations before deployment and will flag several of the patterns above as a matter of lint rather than opinion. Lighthouse gives you the browser-side picture — Shopify notes that Theme Store evaluation uses a minimum average score of 60 across the home, product and collection pages, which is a floor rather than an ambition. And the Web Performance dashboard is where you read field data, meaning what real customers on real connections experienced rather than what your laptop managed.

  • Theme Inspector — which Liquid lines cost server time.
  • Theme Check — which patterns are already known violations.
  • Lighthouse — the browser-side cost, on home, product and collection.
  • Web Performance dashboard — what customers actually experienced.

The honest ranking

If you gave us one week on a slow Shopify store, Liquid would not be the first thing we touched. The order that gets results is: remove or defer render-blocking app scripts, fix the LCP image — loaded eagerly, fetchpriority="high", no fade-in animation delaying it after download, dimensions set — then server-render the content that JavaScript is currently building, and only then go after the Liquid.

That ranking is not a licence to write quadratic templates. Server time is the floor under every other metric, it is the thing a spike in traffic multiplies, and unlike an app script you cannot mitigate it from the browser. But it does mean that a team spending a sprint shaving 80ms off a collection template while a chat widget costs two seconds has optimised the wrong layer, carefully.

Fix the loops when the Theme Inspector says the loops are the problem. Until then, believe the measurement over the instinct.

Questions this raises

Why are Shopify for loops limited to 50 iterations?

Shopify’s Liquid documentation states the limit plainly — “for loops are limited to 50 iterations per page” — and explains that this is why the `paginate` tag exists. Treat it as a guardrail on server render time rather than something to work around.

How many items can a Shopify page paginate through?

The `paginate` tag takes a page size between 1 and 250 and cannot page past the 25,000th item in an array. Beyond that depth you need a different navigation model — filtering, search, or narrower collections — because deep pagination is not something customers use anyway.

Does slow Liquid affect Core Web Vitals?

Indirectly, and mostly through LCP. Liquid render time lands in the server response, which delays every byte after it, so it raises the floor on what LCP can be. But an LCP of several seconds is rarely mostly Liquid — check the image and the render-blocking scripts before rewriting templates.

Are metafields slow in Shopify themes?

Reading a metafield is not expensive in itself; reading one inside a loop is. Shopify’s performance guidance names metafield access inside loops as a pattern to eliminate and advises moving it outside the loop, because each read is data the server has to fetch.

Is `all_products` a bad idea?

It is fine for what it is designed for — up to 20 unique handles on a page. It becomes a bug when a template uses it to assemble a list that can grow, because the twenty-first handle is where it stops working. Use a collection for anything list-shaped.

How do I find out which Liquid is slow?

Install the Shopify Theme Inspector for Chrome. It attributes server render time to individual Liquid lines, which is the only reliable way to tell a genuinely expensive section from one that merely looks complicated.

NEXT STEP

Free store audit

A senior Shopify engineer reviews your storefront, theme performance and checkout, then sends a prioritised list of fixes.