all_products[] Is an O(n) Full Catalog Scan — Here's the O(1) Replacement
all_products['product-handle'] looks like a simple dictionary lookup. It is actually a full catalog scan. Every time your theme calls all_products['hero-product'], Shopify searches every product in your store by handle. Call it three times on a homepage and you have three full-catalog scans per page render. For large stores this can add 5-15 seconds to render time. This guide explains the performance cost and gives you the exact replacement patterns that eliminate the problem.
Why all_products[] is O(n)
all_products is a Liquid global that provides dictionary-style access to products by handle. Internally, each call requires Shopify's rendering engine to execute a sequential search through the entire product catalog to find the matching handle. Unlike a specific collection reference or a type:product section setting — which resolve via indexed database lookups — all_products[] has no pre-built handle index. The search cost scales linearly with catalog size: a store with 10,000 products pays 100x more than one with 100 products, per call.
Where all_products[] appears in themes
Common misuses: hardcoded featured products on the homepage ({% assign hero = all_products['my-hero'] %}), recommendation logic that resolves a list of handles via all_products[], theme customizer settings that store a handle string and resolve it at render time, and navigation links referencing specific products by handle. Every pattern has a direct replacement that eliminates the catalog scan.
The O(1) replacement: type:product section settings
Shopify section settings support type: 'product' which stores a product ID reference — a direct database foreign key, not a handle string. The product loads via an indexed lookup regardless of catalog size: {% assign hero = section.settings.hero_product %}{% if hero != blank %}{{ hero.title }}{% endif %}. Schema: {"type":"product","id":"hero_product","label":"Hero product"}. This gives merchants a product picker in the Theme Editor and resolves in constant time on any catalog size.
When handle-based lookup is unavoidable
If you receive a handle from user input or a metafield and genuinely need to resolve the product, use a metafield of type product_reference instead of a handle string. Product references store the product GID and resolve in O(1). If you cannot change the data shape, at minimum cache the lookup: assign it once outside any loop and reuse the variable rather than calling all_products[] repeatedly for the same handle.
The fix: before and after
// CODE_COMPARISON
Frequently asked questions
- How much slower is all_products[] compared to a section product setting?
On a store with 1,000 products, each all_products[] call adds 50-200ms of render time. Three calls per page adds 150-600ms. A type:product section setting resolves in under 5ms on a store with 100,000 products. The gap grows with catalog size and makes all_products[] unacceptable for production themes.
- Is there any acceptable use case for all_products[] in 2025?
In a theme with under 50 products that will never grow, the performance impact is negligible. But type:product settings are better in every dimension — faster, more maintainable, and merchant-configurable. There is no scenario where all_products[] is the right choice for a production theme.
- Can I use product metafields instead of all_products[] for product references?
Yes. Metafields of type product_reference store the product GID and resolve via indexed lookup. Access: {% assign ref = product.metafields.custom.related.value %}{% if ref != blank %}{{ ref.title }}{% endif %}. This is O(1) regardless of catalog size and works across any resource type.
// SCAN_YOUR_CODE
Does your theme have this bug?
Paste your code. Syphio automatically detects and fixes this error and hundreds of others — in seconds.
Validate my Liquid →