Understanding Instruction Count in Shopify Functions
Learn how Shopify Functions instruction count works, how to measure it with Shopify CLI, and practical ways to optimise Function performance.
If you've built Shopify Functions, you've probably come across the 11 million instruction limit.
It sounds like a lot, but what actually counts as an instruction? More importantly, how do you know how many instructions your Function is using?
What is an instruction?
Shopify Functions run as WebAssembly (Wasm), and Shopify limits the number of WebAssembly instructions that can be executed.
For carts with up to 200 line items, that limit is currently:
11,000,000 instructionsFor carts larger than 200 lines, Shopify dynamically scales the limit based on the number of cart lines.
An instruction isn't the same thing as a line of TypeScript. Something simple like this:
const eligibleLines = input.cart.lines.filter(isEligible);can result in many WebAssembly instructions once compiled and executed.
This means the important question isn't how much TypeScript you've written. It's how much work the compiled Function actually performs.
Repeated work adds up
Consider something like:
for (const line of input.cart.lines) {
const config = JSON.parse(
line.merchandise.product.metafield?.value ?? '{}'
);
// apply some logic
}JSON.parse() might not look particularly expensive, but we're now doing it for every line in the cart.
A useful mental model is:
base cost + (number of lines × work per line)That's not how Shopify literally calculates your limit, but it's a useful way to think about how a Function scales.
A Function that works perfectly with three cart lines might behave very differently with 100 or 200.
How to check instruction count
Fortunately, there's no need to try to calculate this yourself.
Shopify CLI can execute the compiled Function locally against an input:
shopify app function run --input input.jsonYou can also use JSON output:
shopify app function run --input input.json --jsonThe performance measurements from the execution let you see how your Function is behaving against its resource limits.
I like to test the same Function against several different inputs:
10 lines
50 lines
100 lines
200 linesThe interesting part isn't only whether you're below 11 million. It's how the instruction count grows as the input grows.
If your instruction count increases disproportionately as the cart gets larger, it's worth looking at what you're doing repeatedly.
A few optimisation tricks
Once you start measuring instruction count, small changes can sometimes make a surprising difference.
Do work once
Look for anything inside a loop that doesn't need to be there.
Instead of:
for (const line of input.cart.lines) {
const config = JSON.parse(input.shop.config.value);
// ...
}parse it once:
const config = JSON.parse(input.shop.config.value);
for (const line of input.cart.lines) {
// ...
}It's obvious in a small example, but this kind of repeated work is much easier to miss when it's hidden inside a helper.
Consider bitmasks for flags
If you're dealing with a known set of boolean flags, you might not need arrays, objects or sets to represent them.
A bitmask can store several flags in a single integer:
const CUSTOMER = 1 << 0;
const VIP = 1 << 1;
const WHOLESALE = 1 << 2;
const flags = CUSTOMER | VIP;
if (flags & VIP) {
// VIP logic
}It's probably not something I'd introduce into an ordinary React component just to save a few operations.
Inside a Function that's performing the same checks repeatedly, though, simpler data representations can be worth exploring.
Watch repeated searches
Convenient code can hide quite a lot of work.
For example:
for (const line of input.cart.lines) {
const rule = rules.find(
rule => rule.productId === line.merchandise.id
);
}For every cart line, we potentially scan the rules array again.
You could instead build some form of lookup before processing the cart. Whether that's actually cheaper depends on your data, implementation and compiled Function, which brings us back to the important part: measure it.
Don't assume an optimisation is faster because it looks faster in TypeScript.
Input query cost is different
There's another limit worth knowing about that can easily be confused with instruction count.
Shopify Function input queries have a maximum calculated query cost of 30.
For example, Shopify currently assigns different costs to different GraphQL fields. A normal leaf field such as a product variant id or sku costs 1, while requesting a Metafield object costs 3.
This calculation happens from your GraphQL query before the Function executes.
So these are two different things:
Input query cost
↓
Shopify builds your Function input
↓
Your Function executes
↓
WebAssembly instruction countOptimising your GraphQL input and optimising your Function execution are related performance concerns, but they're not the same limit.
Measure, change, repeat
Instruction count is more useful when you treat it as a performance measurement rather than simply a limit you need to stay below.
Use a realistic input, establish a baseline, make a change and run it again.
Before: 6.8m instructions
After: 4.2m instructionsThose numbers are just an example, but that's the workflow that matters.
Shopify Functions operate in a deliberately constrained environment. You can't simply throw more CPU or memory at the problem, so the cost of the code you write becomes much more visible.
And if you've optimised the algorithm, reduced unnecessary work and you're still pushing against the instruction limit, it may be worth considering Rust.
Shopify recommends Rust for computationally intensive Functions and Functions that need to support large carts. JavaScript and TypeScript are great for developer productivity, but their runtime overhead means they can reach instruction limits sooner than equivalent Rust implementations.
That doesn't mean every Shopify Function should be written in Rust. For many Functions, TypeScript is more than enough.
But when performance really matters, sometimes the next optimisation isn't another clever loop or bitmask.
It's choosing the right language for the job.