Guide
Solana compute units explained: limits, budgeting, and program optimization
Solana advertises sub-second finality, but every on-chain program still runs inside a metered virtual machine. The runtime assigns each instruction a compute unit (CU) cost for every operation — arithmetic, memory access, syscalls, cross-program calls — and aborts the transaction when the budget is exhausted. Users see this as exceeded compute budget or a silent simulation failure; developers see failed mints, broken swaps, and support tickets. This guide explains how compute metering works, how to set explicit CU limits and priority fees, how to read consumption from simulation logs, and the optimization patterns that keep complex Anchor programs within budget on mainnet.
What compute units measure
Unlike Ethereum gas, which prices both computation and storage in one unit, Solana separates base transaction fees (fixed lamports per signature) from compute (how much work validators execute) and priority fees (how much you bid for scarce block space). Compute units are the internal accounting token for work inside the BPF (Berkeley Packet Filter) runtime that executes on-chain programs.
Every instruction your transaction invokes — System Program transfer, SPL Token transfer, your custom program, a CPI into Metaplex — consumes CUs from a shared per-transaction budget. The meter decrements on:
- Native program instructions and syscalls (logging, account data reads, CPI setup).
- BPF bytecode execution inside your program (loops, branches, memory copies).
- Account deserialization and writes back to the account model.
- Nested cross-program invocations — each CPI layer adds overhead.
When the remaining budget hits zero before the last instruction finishes, the runtime rolls back state changes and returns an error. You still pay the base fee for a failed transaction that lands on-chain — another reason to simulate first.
Default limits and where they apply
Out of the box, a Solana transaction gets a compute budget of 200,000 CUs (200k). That is enough for simple transfers and many standard SPL operations, but complex DeFi routes, NFT mints with metadata, or programs with heavy loops can exceed it quickly.
Per-transaction ceiling
You can raise the limit up to 1,400,000 CUs (1.4M) per transaction
using the Compute Budget program's setComputeUnitLimit instruction. Wallets
and SDKs often add this automatically when they detect a complex route (Jupiter swaps,
compressed NFT mints). Setting the limit too high without need wastes block space
scheduling headroom; setting it too low guarantees failure.
Per-block account write cap
Separately, Solana enforces a per-block limit on account data writes for hot accounts (historically around 12 MB of account data churn per block across heavily contended accounts). This is not the same as CU exhaustion, but it produces similar user-facing failures during popular mints when thousands of transactions hammer the same program-owned accounts. Mitigations include sharding state across multiple accounts, using state compression for NFT-scale mints, and designing programs that spread writes across PDAs.
Per-instruction cap inside a program
Individual program invocations also carry an internal CU cap (200k per top-level instruction by default, with CPI calls drawing from the transaction-wide pool). Deep CPI stacks in composable DeFi can burn budget faster than flat instruction counts suggest.
Compute Budget instructions: limit and price
The native Compute Budget program (address
ComputeBudget111111111111111111111111111111) exposes two instructions
clients prepend to transactions:
setComputeUnitLimit
Declares the maximum CUs this transaction may consume. Must appear before the instructions that need the budget. Typical values:
- 200,000 — default; fine for payments and simple token ops.
- 400,000–600,000 — many DEX swaps and NFT mints.
- 1,000,000+ — heavy routes; verify with simulation, do not guess.
In JavaScript with @solana/web3.js, use
ComputeBudgetProgram.setComputeUnitLimit({ units: 400_000 }) as the first
instruction in your Transaction or versioned transaction message.
setComputeUnitPrice
Sets the priority fee rate in micro-lamports per compute unit. Total priority tip = price × requested CU limit (capped by actual consumption). This is how congestion pricing works: during a memecoin launch, the same swap might need 50,000 micro-lamports/CU to land in the next few slots versus 1,000 micro-lamports/CU on a quiet afternoon. Our priority fees guide covers wallet UX and troubleshooting dropped transactions; the key link here is that raising CU limit without raising price may still leave you unscheduled when blocks are full.
Order matters: compute budget instructions should be first. Wallets that rebuild transactions often strip and re-add them — if you integrate wallet-adapter, expose simulation results so users do not sign under-budgeted txs.
Reading CU consumption from simulation
Never ship a complex transaction without measuring actual CU use. Call
simulateTransaction on your RPC with
replaceRecentBlockhash: true and inspect the response:
unitsConsumed— total CUs the simulated run used.- Program logs —
Program consumed XXXXX of YYYYY compute unitsper invoked program; pinpoints which instruction blew the budget. err—InstructionErrorwithComputationalBudgetExceededmeans you need a higher limit or leaner code.
Add headroom: if simulation reports 380,000 CUs consumed, set the limit to 450,000–500,000
to absorb mainnet state drift (slightly different account sizes, feature flags). For
production backends, log unitsConsumed on every failure and alert when
p95 usage crosses 80% of your configured limit — that is an early warning before users
hit walls during traffic spikes.
Anchor's anchor test and solana program log show the same
consumption lines locally. Profile on devnet with realistic account sizes; empty test
accounts understate deserialization cost.
Program optimization patterns
Client-side CU limits are a band-aid if your program is inefficient. Common wins in Rust/Anchor programs:
Minimize account data reads and writes
Deserializing a large account costs CUs proportional to size. Store hot fields in fixed layouts, use zero-copy accounts where Anchor supports them, and avoid loading accounts you do not need. Every byte written back triggers rent-exempt balance checks and syscall overhead.
Keep CPI chains shallow
Each CPI adds base overhead plus callee execution. Batch operations in one program when security allows instead of ping-ponging through five external programs. When CPI is required, pass the minimal account set — extraneous accounts still cost meta processing.
Avoid unbounded loops on-chain
Validators cap per-transaction work; loops over user-supplied lengths are exploit bait and CU traps. Cap iteration counts in instruction data, use merkle proofs for large sets, or move heavy aggregation off-chain with on-chain verification — see our Merkle trees guide for the pattern.
Logging discipline
msg! and sol_log syscalls cost CUs. Verbose logging in
production instructions adds up. Gate debug logs behind feature flags; keep one concise
error path for failures users must diagnose.
Choose efficient crypto and math
On-chain Ed25519 verify, bigint math, and string handling are expensive. Prefer precomputed constants, fixed-point integers, and verifying signatures off-chain when trust model allows — but never skip verification that secures funds.
Common failure modes
- ComputationalBudgetExceeded — limit too low or program too heavy; simulate, raise limit, or optimize code.
- Blockhash not found / expired — unrelated to CUs but often confused; see blockhash expiry.
- Simulation succeeds, mainnet fails — state changed between simulate and send (account closed, balance dropped), or different feature set on RPC vs leader; re-simulate immediately before broadcast.
- Priority fee too low despite adequate CUs — transaction never included; increase micro-lamports/CU or wait for quieter slots.
- Account write lock contention — not CU exhaustion but parallel mints failing; architectural fix, not a higher limit.
Production checklist
- Simulate every non-trivial transaction path; record
unitsConsumedper instruction type. - Set
setComputeUnitLimitto simulated usage plus 15–25% headroom. - Attach dynamic
setComputeUnitPricefrom RPC fee estimators during congestion. - Profile Anchor programs on devnet with production-sized account data.
- Cap loops, minimize CPI depth, and audit logging in release builds.
- Monitor failure rates for
ComputationalBudgetExceededin backend logs and Sentry. - Document recommended CU limits in your dApp API so integrators do not under-budget.
- Re-benchmark after Solana runtime upgrades — CU costs per syscall occasionally change.
Key takeaways
- Compute units meter BPF execution; default 200k CUs per transaction, max 1.4M with an explicit instruction.
setComputeUnitLimitandsetComputeUnitPricebelong at the start of complex transactions.- Simulation
unitsConsumedand program logs tell you exactly where budget runs out. - Optimization beats brute-force limit increases — smaller accounts, fewer CPIs, bounded loops.
- CU limits and priority fees solve different problems; congested networks need both tuned.
Related reading
- Solana priority fees explained — micro-lamports per CU and landing txs during congestion
- Solana transaction simulation explained — preflight, logs, and failure decoding
- Solana Anchor framework explained — account constraints and testing workflows
- Versioned transactions and lookup tables — fitting more instructions in one tx