---
title: Optimize instruction counts
description: >-
  Learn how to measure, profile, and reduce the number of WebAssembly
  instructions that your Shopify Functions execute.
source_url:
  html: 'https://shopify.dev/docs/apps/build/functions/optimize-instruction-counts'
  md: 'https://shopify.dev/docs/apps/build/functions/optimize-instruction-counts.md'
api_type: functions
---

# Optimize instruction counts

Shopify Functions run inside critical buyer flows, such as cart and checkout. Every WebAssembly instruction that your function executes adds latency for the buyer, and a single checkout request can run many functions. The fewer instructions your function uses, the faster checkout is for your merchants' customers, and the more headroom your function has for large carts and complex configurations.

This guide explains how instruction counts work, where to find them, and how to reduce them. The biggest improvements usually come from the following steps, in order:

1. [Migrate from JavaScript to Rust](#migrate-from-javascript-to-rust).
2. [Request only the input that your function needs](#reduce-your-functions-input).
3. [Profile your function](#profile-your-function) and optimize the hot paths, optionally [with an AI agent](#optimize-with-an-ai-agent).
4. [Track instruction counts in your tests](#track-instruction-counts-in-your-tests) so that they don't regress.

***

## How instruction counts work

Shopify runs your function's WebAssembly module and counts every instruction that it executes. For carts with up to 200 line items, a function can execute up to 11 million instructions. For larger carts, the limit scales proportionally with the number of cart lines. If your function exceeds the limit, then Shopify stops it and reports an [`InstructionCountLimitExceededError`](https://shopify.dev/docs/apps/build/functions/monitoring-and-errors). For the full list of limits, refer to [function resource limits](https://shopify.dev/docs/api/functions/latest#dynamic-limits).

The instruction count for a run depends on the following factors:

* **Language and runtime**: Languages that compile directly to WebAssembly, such as Rust, execute your logic as native WebAssembly instructions. JavaScript functions run on a JavaScript engine that interprets your code, so each line of JavaScript costs many more WebAssembly instructions.
* **Input size**: Your function spends instructions reading its input. Larger input queries, more cart lines, and larger metafield values all increase the count.
* **Logic**: Loops over cart lines, string manipulation, memory allocation, and logging all execute instructions.

Instruction counts are deterministic. The same WebAssembly module and the same input produce the same count, so you can measure the effect of a change locally.

**Note:**

Staying under the limit isn't the goal. A function that runs close to the limit on a typical cart can fail on a larger one, and every instruction adds checkout latency. Aim to keep your instruction counts as low as you reasonably can.

***

## Measure instruction counts

You can find the instruction counts of your function's production runs in the Dev Dashboard, and measure them locally with Shopify CLI as you make changes.

### In the Dev Dashboard

The [Dev Dashboard](https://dev.shopify.com/dashboard/) shows the instruction count for each function run, along with the limit that applied to that run. Look for runs with high instruction counts, and use their input to reproduce them locally. Run input is available when your app has the [access scopes required by the function's input query](https://shopify.dev/docs/apps/build/functions/monitoring-and-errors#access-function-run-details).

### Locally with Shopify CLI

Use [`app function run`](https://shopify.dev/docs/api/shopify-cli/app/app-function-run) to execute your function locally with a JSON input. The command runs the WebAssembly module that's already built, so build your function first. Shopify CLI prints the function output, followed by benchmark results that include the instruction count and the limit for that input:

## Terminal

```terminal
shopify app function build
shopify app function run --input input.json
```

The end of the output shows the limits that apply to that input, and the measurements from the run. For example:

## Example benchmark results

```terminal
Resource Limits


Input Size: 125.00KB
Output Size: 19.53KB
Instructions: 11M




     Benchmark Results


Name: my-function
Linear Memory Usage: 1088KB
Instructions: 2.364917M
Input Size: 3.42KB
Output Size: 412B
Module Size: 94KB
```

The `Instructions` value under **Benchmark Results** is the number of instructions that your function executed. The `Instructions` value under **Resource Limits** is the limit for that input, which scales with the number of cart lines.

If your function has more than one target, then Shopify CLI prompts you to choose the export to run. To skip the prompt, pass `--export` with the `export` value of the target from your `shopify.extension.toml` file.

To measure with real input instead of input that you write yourself, replay a run from your dev store. While [`app dev`](https://shopify.dev/docs/api/shopify-cli/app/app-dev) is running, Shopify runs your function whenever you trigger it on your dev store, for example by adding products to a cart. Shopify CLI saves the input and output of each of these runs to your app's `.shopify/logs` directory. Use [`app function replay`](https://shopify.dev/docs/api/shopify-cli/app/app-function-replay) to re-run one of those executions locally with the same input. For more information, refer to [Test and debug Shopify Functions](https://shopify.dev/docs/apps/build/functions/test-debug-functions#replay-a-function-locally).

When you measure, use inputs that reflect your heaviest real workloads, such as carts with many line items or merchants with large configurations. A function that performs well on a one-line cart can behave very differently on a 200-line cart.

**Info:**

Pass `--json` to `app function run` to get the output and measurements as JSON. The `instructions` field contains the instruction count, which you can use in your own scripts and reports.

***

## Migrate from Java​Script to Rust

JavaScript functions are compiled with [Javy](https://shopify.dev/docs/apps/build/functions/programming-languages/javascript-for-functions#javy), which produces a WebAssembly module that's linked to a JavaScript engine at runtime. The engine interprets your code as it runs, so a JavaScript function executes significantly more instructions than the equivalent Rust function. Migrating to [Rust](https://shopify.dev/docs/apps/build/functions/programming-languages/rust-for-functions) is usually the step that will have the largest impact on reducing your function's instruction count.

You don't need to create a new extension to migrate. Add your Rust code alongside your JavaScript code in the same function extension, and use `shopify.extension.toml` to choose which language to build. Because the extension doesn't change, merchants don't need to do anything, and you can switch back to JavaScript if you find an issue. For step-by-step instructions, refer to [Migrating from JavaScript](https://shopify.dev/docs/apps/build/functions/programming-languages/rust-for-functions#migrating-from-javascript).

Before you migrate, collect a set of test fixtures from your JavaScript function's real runs. You don't need to add any instrumentation to collect them. While `app dev` is running, Shopify CLI saves every run on your dev store as a log file in `.shopify/logs`, and the Dev Dashboard shows the input and output of production runs when your app has the [required access scopes](https://shopify.dev/docs/apps/build/functions/monitoring-and-errors#access-function-run-details). To turn these runs into fixtures, refer to [Add additional test fixtures](https://shopify.dev/docs/apps/build/functions/test-debug-functions#add-additional-test-fixtures). Use the fixtures to confirm that the Rust function returns the same output for the same input, and to compare instruction counts before and after. For more information, refer to [Track instruction counts in your tests](#track-instruction-counts-in-your-tests). AI coding agents are effective at porting function logic between languages when they have fixtures to check their work against.

### If you stay on Java​Script

If you can't migrate yet, then you can still reduce your JavaScript function's instruction count:

* Use the [latest version of Shopify CLI](https://shopify.dev/docs/api/shopify-cli#upgrade) and version 2.0.0 or higher of the [`@shopify/shopify_function`](https://npm.im/@shopify/shopify_function) package.
* Keep your bundle small. Remove unused npm dependencies, and prefer small, focused libraries. Every bundled dependency adds code that the JavaScript engine needs to load and run.
* Apply the [input](#reduce-your-functions-input) and [logic](#write-efficient-function-logic) techniques in this guide, which apply to every language.

***

## Reduce your function's input

The more data your function receives, the more instructions it takes to read and process it. Keep your input as small as possible:

* **Query only the fields that you use.** Review your input query, such as `run.graphql`, and remove any fields that your function doesn't read.
* **Let Shopify evaluate conditions for you.** Fields such as `hasAnyTag` and `inAnyCollection` return a single boolean instead of lists that your function needs to search. Use [input query variables](https://shopify.dev/docs/apps/build/functions/input-queries/use-variables-input-queries) to make their arguments configurable per merchant.
* **Read JSON metafields with `jsonValue`.** Query `jsonValue` instead of parsing a JSON string from `value` in your function code.
* **Keep configuration small and precomputed.** Do as much work as possible when a merchant saves their settings, and store the result in a [metafield](https://shopify.dev/docs/apps/build/functions/input-queries/metafields-for-input-queries) in the shape that your function needs. For example, store a lookup of product IDs instead of rules that your function needs to evaluate on every run.

***

## Write efficient function logic

The following practices reduce instruction counts in any language:

* **Return early.** If your function has nothing to do for a cart, such as when a discount doesn't apply, then return an empty result before doing any other work.
* **Avoid nested loops over cart lines.** Comparing every line with every other line grows quickly with cart size. Build a map or set in one pass, and then look values up.
* **Avoid repeated work.** Compute values once and reuse them, instead of recalculating them for every cart line.
* **Minimize string work.** String formatting, concatenation, and case conversion all execute instructions. Compare IDs and values directly where you can.
* **Remove debug logging.** Logging executes instructions even when logs are truncated. Keep only the logs that you need to diagnose production runs. Because function runs are deterministic, you can add detailed logging to a local build instead, and reproduce a production run with its input by using [`app function replay`](https://shopify.dev/docs/api/shopify-cli/app/app-function-replay) or `app function run`.

For Rust functions, also consider the following practices:

* Use the latest version of the [`shopify_function`](https://crates.io/crates/shopify_function) crate. Newer versions include performance improvements.
* Borrow data instead of cloning it, and avoid allocating new `String` and `Vec` values inside loops.
* Use `to_ascii_lowercase` and `to_ascii_uppercase` instead of Unicode-aware case conversion when your data allows it.
* Experiment with the `opt-level` setting in the `[profile.release]` section of your `Cargo.toml`. Function templates use `opt-level = "z"` to keep the binary small, but other levels can produce fewer instructions. Measure both the instruction count and the binary size, because your compiled binary must stay under 256 kB.

***

## Profile your function

A profile shows which functions in your code execute the most instructions, so you can focus on the parts that matter. Shopify CLI generates profiles that you can open in [Speedscope](https://www.speedscope.app/).

**Note:**

Profiles are most useful for Rust and other languages that compile directly to WebAssembly. JavaScript functions built with Javy don't include function names in their WebAssembly module, so every frame in the profile appears as `<unknown>`.

Function names help you read a profile. By default, the Rust function templates strip names from the binary, and Shopify CLI runs `wasm-opt`, which also removes them. To profile your function, complete the following steps:

1. In your `Cargo.toml`, stop stripping symbols from release builds:

   ## Cargo.toml

   ```toml
   [profile.release]
   lto = true
   opt-level = "z"
   strip = false
   ```

2. In your `shopify.extension.toml`, disable `wasm-opt`:

   ## shopify.extension.toml

   ```toml
   [extensions.build]
   command = "cargo build --target=wasm32-unknown-unknown --release"
   path = "target/wasm32-unknown-unknown/release/[RUST-PACKAGE-NAME].wasm"
   wasm_opt = false
   ```

3. Rebuild your function so that the new settings take effect, and then run it with the `--profile` flag, using an input that represents a heavy workload:

   ## Terminal

   ```terminal
   shopify app function build
   shopify app function run --input input.json --profile
   ```

   Shopify CLI writes a profile file with a `.perf` extension to your function's directory. The file is named after your WebAssembly module.

4. Open [Speedscope](https://www.speedscope.app/) and load the `.perf` file. Each sample is weighted by instructions executed, so the widest frames are the functions that execute the most instructions. The **Left Heavy** view groups identical call stacks together, which makes the most expensive code paths easy to find.

5. Optimize the most expensive functions, rebuild, and profile again to confirm that the instruction count dropped.

6. When you're done profiling, restore your original `strip` and `wasm_opt` settings. Stripping names and running `wasm-opt` keep your deployed binary small.

### Optimize with an AI agent

AI coding agents can work through a profile and try optimizations much faster than you can by hand. Because instruction counts are deterministic, an agent can measure every change it makes and keep only the ones that help. To get good results, give the agent a way to check both correctness and performance:

* **Shopify context**: Install the [Shopify AI Toolkit](https://shopify.dev/docs/apps/build/ai-toolkit) so that your agent has current guidance for Shopify Functions and Shopify CLI.
* **Test fixtures**: A set of fixtures that cover your real workloads, so that the agent can confirm the function output doesn't change. For more information, refer to [Add additional test fixtures](https://shopify.dev/docs/apps/build/functions/test-debug-functions#add-additional-test-fixtures).
* **An instruction count test**: A test that reports instruction counts for each fixture, as described in [Track instruction counts in your tests](#track-instruction-counts-in-your-tests).
* **A profile**: The `.perf` file from [profiling your function](#profile-your-function). It's a plain text file, so agents can read it directly.

For example, you might give your agent a prompt like the following:

## Example prompt

```text
Reduce the instruction count of the Shopify Function in extensions/my-function.


- Run `npm test` in the function directory to check correctness and to see
  the instruction count for each fixture. Every fixture must keep producing
  the same output.
- Run `shopify app function build`, and then run
  `shopify app function run --input input.json --export <export> --profile`
  with the input from a large fixture to generate a profile. Read the .perf
  file to find the most expensive code paths.
- Make one change at a time, rebuild, and measure. Keep only changes that
  reduce instruction counts.
- Keep the compiled WebAssembly binary under 256 kB.
- When you're done, summarize each change and its effect on instruction counts.
```

Review the agent's changes as you would any other code change before you deploy.

***

## Track instruction counts in your tests

Functions generated from a template include integration tests that use the [`@shopify/shopify-function-test-helpers`](https://www.npmjs.com/package/@shopify/shopify-function-test-helpers) package. In version 1.1.0 and higher, the `runFunction` helper returns a `metadata` object with measurements for each run:

| Property | Description |
| - | - |
| `instructionCount` | The number of WebAssembly instructions that the function executed. |
| `memoryUsageKiB` | The memory that the function used, in kibibytes. |
| `moduleSizeKiB` | The size of the compiled WebAssembly module, in kibibytes. |

You can use these measurements to build your own reports, or to fail your tests when a change increases the instruction count. For more information about integration tests and fixtures, refer to [Writing Wasm integration tests for functions](https://shopify.dev/docs/apps/build/functions/test-debug-functions#writing-wasm-integration-tests-for-functions).

### Set an instruction count baseline

The following test requires version 1.1.0 or higher of the test helpers. If your function uses an earlier version, then upgrade it from your function's directory:

## Terminal

```terminal
npm install --save-dev @shopify/shopify-function-test-helpers@^1.1.0
```

The following version of the default integration test keeps its existing checks and adds an instruction count baseline. For every fixture in `tests/fixtures/`, it validates the fixture, checks the output, prints a table of instruction counts, and fails if a fixture uses more than 5% more instructions than its recorded baseline. Replace the contents of `tests/default.test.js` with it:

## tests/default.test.js

```js
import path from "path";
import fs from "fs";
import { describe, beforeAll, afterAll, test, expect } from "vitest";
import {
  buildFunction,
  getFunctionInfo,
  loadSchema,
  loadInputQuery,
  loadFixture,
  validateTestAssets,
  runFunction,
} from "@shopify/shopify-function-test-helpers";


const BASELINE_PATH = path.join(__dirname, "instruction-count-baseline.json");
const UPDATE_BASELINE = process.env.UPDATE_INSTRUCTION_BASELINE === "1";
// Allow for small differences between toolchain versions.
const TOLERANCE = 1.05;


const baseline = fs.existsSync(BASELINE_PATH)
  ? JSON.parse(fs.readFileSync(BASELINE_PATH, "utf8"))
  : {};
const measured = {};


describe("Default Integration Test", () => {
  let schema;
  let schemaPath;
  let targeting;
  let functionRunnerPath;
  let wasmPath;


  beforeAll(async () => {
    const functionDir = path.dirname(__dirname);
    await buildFunction(functionDir);
    ({ schemaPath, functionRunnerPath, wasmPath, targeting } =
      await getFunctionInfo(functionDir));
    schema = await loadSchema(schemaPath);
  }, 45000);


  afterAll(() => {
    console.table(measured);
    if (UPDATE_BASELINE) {
      fs.writeFileSync(BASELINE_PATH, `${JSON.stringify(measured, null, 2)}\n`);
    }
  });


  const fixturesDir = path.join(__dirname, "fixtures");
  const fixtureFiles = fs
    .readdirSync(fixturesDir)
    .filter((file) => file.endsWith(".json"));


  fixtureFiles.forEach((fixtureFile) => {
    test(`runs ${fixtureFile}`, async () => {
      const fixture = await loadFixture(path.join(fixturesDir, fixtureFile));
      const queryPath = targeting[fixture.target].inputQueryPath;
      const inputQueryAST = await loadInputQuery(queryPath);


      const validationResult = await validateTestAssets({
        schema,
        fixture,
        inputQueryAST,
      });
      expect(validationResult.inputQuery.errors).toEqual([]);
      expect(validationResult.inputFixture.errors).toEqual([]);
      expect(validationResult.outputFixture.errors).toEqual([]);


      // Use the export from shopify.extension.toml, so that the same fixtures
      // work if you switch your function between languages.
      const runResult = await runFunction(
        { ...fixture, export: targeting[fixture.target].export ?? fixture.export },
        functionRunnerPath,
        wasmPath,
        queryPath,
        schemaPath,
      );
      expect(runResult.error).toBeNull();
      expect(runResult.result.output).toEqual(fixture.expectedOutput);


      const { instructionCount } = runResult.metadata;
      measured[fixtureFile] = instructionCount;


      if (UPDATE_BASELINE) return;


      expect(
        baseline[fixtureFile],
        `No baseline for ${fixtureFile}. Run with UPDATE_INSTRUCTION_BASELINE=1.`,
      ).toBeDefined();
      expect(instructionCount).toBeLessThanOrEqual(
        Math.ceil(baseline[fixtureFile] * TOLERANCE),
      );
    }, 10000);
  });
});
```

**Note:**

Keep the baseline checks in the same test file as your other integration tests. Each test file that calls `buildFunction()` starts its own build, and Vitest runs test files in parallel, so separate files can build the same function at the same time.

To record the baseline, run the tests with the `UPDATE_INSTRUCTION_BASELINE` environment variable, and commit the generated `tests/instruction-count-baseline.json` file:

## Terminal

```terminal
UPDATE_INSTRUCTION_BASELINE=1 npm test
```

After that, `npm test` fails whenever a change pushes a fixture's instruction count above its baseline. When you make an optimization, record the baseline again so that your improvement becomes the new standard.

**Info:**

Run your function's tests in your continuous integration pipeline to catch instruction count regressions before you deploy. Make sure that your fixtures include your largest realistic carts, because that's where instruction counts grow the most.

***

## Next steps

* Learn about the [language support](https://shopify.dev/docs/apps/build/functions/programming-languages) that's available for Shopify Functions.
* Get familiar with [testing and debugging practices](https://shopify.dev/docs/apps/build/functions/test-debug-functions) for Shopify Functions.
* Review the [resource limits](https://shopify.dev/docs/api/functions/latest#limitations) that apply to all functions.
* Learn how to [handle errors in production](https://shopify.dev/docs/apps/build/functions/monitoring-and-errors).

***
