---
title: Filter collections before entering loops
description: >-
  Pre-filter collections using Liquid filters before loops to reduce
  per-iteration conditional overhead that multiplies across all iterations and
  increases TTFB.
source_url:
  html: >-
    https://shopify.dev/docs/storefronts/themes/best-practices/performance/avoid-conditionals-in-loops
  md: >-
    https://shopify.dev/docs/storefronts/themes/best-practices/performance/avoid-conditionals-in-loops.md
api_name: liquid
---

# Filter collections before entering loops

Pre-filter collections using Liquid filters before loops. Move complex conditional logic outside of [`for`](https://shopify.dev/docs/api/liquid/tags/for) loops to reduce processing overhead that multiplies across iterations.

***

## Why

Conditional checks have a processing cost. When placed inside a [`for`](https://shopify.dev/docs/api/liquid/tags/for) loop, the number of iterations multiplies this cost. A collection with 50 products and 3 conditional checks per iteration means 150 conditional evaluations. If you can pre-filter the collection down to 12 matching products before entering the loop, then you evaluate 12 iterations with 1 conditional each: 12 evaluations instead of 150.

Complex conditions with multiple `and`/`or` operators are more expensive because each operator requires a separate evaluation. This processing adds directly to server-side Liquid rendering time, increasing TTFB, which in turn delays FCP and LCP.

***

## How

Use Liquid filters to narrow the collection before the loop starts:

* [`where`](https://shopify.dev/docs/api/liquid/filters/where): Filters objects by a property value. `collection.products | where: "available"` returns only available products.
* [`map`](https://shopify.dev/docs/api/liquid/filters/map): Extracts a single property from each object. Useful for building arrays to check against.
* [`sort`](https://shopify.dev/docs/api/liquid/filters/sort): Sorts objects by a property. Sort once before the loop rather than sorting within the loop.
* [`compact`](https://shopify.dev/docs/api/liquid/filters/compact): Removes `nil` values from an array after mapping or filtering.
* [`uniq`](https://shopify.dev/docs/api/liquid/filters/uniq): Removes duplicates from an array.

You can chain multiple filters together to apply several conditions before the loop:

```liquid
{% assign filtered = collection.products | where: "available" | sort: "price" %}
```

For conditions that can't be expressed as `where` filters, such as price ranges, tag checks, and compound logic, pre-compute a lookup variable before the loop to simplify the in-loop check.

***

## Examples

### Basic pre-filtering with `where`

Less performant:

```liquid
{%- for product in collection.products -%}
  {% if product.available %}
    {{ product.title }} - {{ product.price | money }}
  {% endif %}
{%- endfor -%}
```

More performant:

```liquid
{% assign available_products = collection.products | where: "available" %}


{%- for product in available_products -%}
  {{ product.title }} - {{ product.price | money }}
{%- endfor -%}
```

### Compound filtering: available, tagged, and within a price range

When you need multiple conditions, apply as many as possible before the loop using filters, then handle the remaining conditions with a single in-loop check:

```liquid
{%- comment -%}
  Pre-filter: available products only.
  The where filter handles the boolean check outside the loop.
{%- endcomment -%}
{% assign candidates = collection.products | where: "available" %}


{%- comment -%}
  In-loop: check tag and price range.
  These conditions can't be expressed with the where filter,
  but the loop now iterates over fewer products.
{%- endcomment -%}
{%- for product in candidates -%}
  {% if product.tags contains 'featured' and product.price >= 1000 and product.price <= 5000 %}
    <div class="product-card">
      {{ product.featured_image | image_url: width: 400 | image_tag: loading: 'lazy' }}
      <h3>{{ product.title }}</h3>
      <p>{{ product.price | money }}</p>
    </div>
  {% endif %}
{%- endfor -%}
```

### Pre-compute a lookup variable

When you need to check a product against a list of values, build the list before the loop so the lookup is a simple `contains` check:

```liquid
{%- comment -%}
  Build a comma-separated list of promoted product handles
  from a metafield, then check membership inside the loop.
{%- endcomment -%}
{% assign promoted_handles = section.settings.promoted_products | map: "handle" %}


{%- for product in collection.products -%}
  {% if promoted_handles contains product.handle %}
    <div class="product-card product-card--promoted">
      {{ product.featured_image | image_url: width: 400 | image_tag: loading: 'lazy' }}
      <h3>{{ product.title }}</h3>
    </div>
  {% else %}
    <div class="product-card">
      {{ product.featured_image | image_url: width: 400 | image_tag: loading: 'lazy' }}
      <h3>{{ product.title }}</h3>
    </div>
  {% endif %}
{%- endfor -%}
```

### Chaining multiple filters

Combine `where`, `sort`, and other filters to build the exact dataset before iterating:

```liquid
{%- comment -%}
  Get available products, sort by price ascending.
  The loop body has no conditionals at all.
{%- endcomment -%}
{% assign sorted_available = collection.products | where: "available" | sort: "price" %}


{%- for product in sorted_available -%}
  <div class="product-card">
    {{ product.featured_image | image_url: width: 400 | image_tag: loading: 'lazy' }}
    <h3>{{ product.title }}</h3>
    <p>{{ product.price | money }}</p>
  </div>
{%- endfor -%}
```

***

## Testing

* Use the [Theme Inspector](https://shopify.dev/docs/storefronts/themes/tools/theme-inspector) Chrome extension to identify expensive sections. Look for sections with high Liquid rendering times that contain loops.
* Compare TTFB in the [Chrome DevTools Network panel](https://developer.chrome.com/docs/devtools/network) before and after pre-filtering. Filter to the document request and check the **Waiting for server response** time.
* Focus on pages with the largest collections: collection pages with 50 or more products, search results, and pages with many section blocks.

***

## References

* [`if`](https://shopify.dev/docs/api/liquid/tags/if) tag
* [`for`](https://shopify.dev/docs/api/liquid/tags/for) tag
* [`where`](https://shopify.dev/docs/api/liquid/filters/where) filter
* [`map`](https://shopify.dev/docs/api/liquid/filters/map) filter
* [`sort`](https://shopify.dev/docs/api/liquid/filters/sort) filter
* [`compact`](https://shopify.dev/docs/api/liquid/filters/compact) filter
* [`uniq`](https://shopify.dev/docs/api/liquid/filters/uniq) filter
* [`contains`](https://shopify.dev/docs/api/liquid/basics#contains) operator
* [`product.available`](https://shopify.dev/docs/api/liquid/objects/product#product-available)
* [`product.price`](https://shopify.dev/docs/api/liquid/objects/product#product-price)
* [`product.tags`](https://shopify.dev/docs/api/liquid/objects/product#product-tags)
* [Debugging common causes for slow loading in Shopify Liquid storefronts](https://performance.shopify.com/blogs/blog/debugging-common-causes-for-slow-loading-in-shopify-liquid-storefronts)
* [Avoid deeply nested Liquid loops](https://shopify.dev/docs/storefronts/themes/best-practices/performance/avoid-deeply-nested-liquid-loops)
* [Move operations outside loops](https://shopify.dev/docs/storefronts/themes/best-practices/performance/move-operations-outside-loops)
* [Optimize metafield access](https://shopify.dev/docs/storefronts/themes/best-practices/performance/move-metafield-access-outside-loops)

***
