Avoid deeply nested Liquid loops
Flatten nested loop structures using Liquid filters instead of iterating over data inside another loop.
Nested loops have multiplicative cost. Each iteration of the outer loop runs the full inner loop, so the total number of operations is outer iterations × inner iterations. With 10 products and 10 variants each, that's 100 operations. With 50 products and 10 variants, it's 500, and each of those iterations might access additional Liquid objects, compounding the cost further.
This is O(n²) complexity: doubling the collection size roughly quadruples the rendering work. The cost grows faster than the content does, which is why a theme that performs well on a small catalog can become slow on a larger one.
The fix is to flatten the loop structure by pre-computing what you need before the outer loop starts.
Anchor to Use ,[object Object], and ,[object Object], to find a matchUse where and first to find a match
where and first to find a matchIf the inner loop looks for the first item that meets a condition, then replace it with a where filter assigned before the loop:
Both versions look at the same products. The difference is where the work happens: the for loop evaluates a tag, a condition, and a loop iteration for every product it touches, while where performs the comparison in a single filter call. That makes the rewrite worth it when matches are rare or absent, because then the loop runs the full inner iteration. If the first item almost always matches, then the loop breaks on its first pass and the two are close, so reach for where when you can't rely on an early match.
Anchor to Use ,[object Object], to pre-build a lookupUse map to pre-build a lookup
map to pre-build a lookupIf the inner loop checks membership in another array, then use map to extract the values you need before the outer loop starts, then use contains for a direct lookup:
Anchor to Use ,[object Object], instead of a loop to output list valuesUse join instead of a loop to output list values
join instead of a loop to output list valuesIf the inner loop just concatenates values with a separator, then join does the same work without a nested iteration:
Anchor to ExamplesExamples
Anchor to Flatten a collection list sectionFlatten a collection list section
A collection list section that shows a preview image, an in-cart badge, and an option summary for each collection can end up with three nested loops inside one outer loop. Precompute each lookup before the outer loop, and the section renders with one pass per collection:
Before
sections/collection-list.liquid
The cart lookup is the expensive one, because it's the only loop that can't exit early: it compares every product against every cart line. Build the cart's product IDs once, above the outer loop, and it becomes a single contains check:
After
sections/collection-list.liquid
The remaining loop iterates over cart lines, which is a short list bounded by what the customer added, rather than over the catalog. Note that cart_product_ids is assigned once for the whole section, not once per collection, because the cart doesn't change while the section renders.
When more than one block needs the same derived list, assign it once at the top of the section and let each block read it. This keeps the cost fixed no matter how many blocks the merchant adds:
Shared lookup
sections/product.liquid
Assigning available_variants inside each block would repeat the same filter for every block that needs it. Hoisting it above the block loop runs it once per render.
Anchor to TestingTesting
- Use the Theme Inspector Chrome extension to visualize Liquid rendering performance.
- Check the flame graph visualization to see the timeline of rendering.
- Use the sandwich view to aggregate execution times and find repetitive operations.
- Sort by Self time to identify expensive operations called frequently.
- Focus on loops iterating many times with complex operations inside.
- Compare TTFB in the Chrome DevTools Network panel before and after optimizations.
Anchor to ReferencesReferences
wherefiltermapfilterfirstfilterjoinfiltercontainsoperator- In-depth Liquid render analysis with Theme Inspector
- Debugging common causes for slow loading in Shopify Liquid storefronts
- Move operations outside loops
- Optimize metafield access
- Limit pagination depth
- Avoid excessive conditionals in loops
- Avoid repetitive filter calls