Monad · Advanced

How to write Solidity that runs in parallel on Monad

Practical patterns for parallel-friendly Solidity: avoid shared counters, partition storage by user, keep hot paths off global state, and measure contention with a dependency graph.

By , founder of Monsmith9 min read

Monad executes the transactions in a block in parallel and re-runs any that conflicted. Your contract is correct either way. The question is how often it conflicts, because every conflict is a transaction that ran twice and a core that sat idle. This guide is about writing contracts that rarely conflict.

The one rule

Pattern 1: No global hot slots

A single totalTransactions counter incremented by every call is a serialisation point: every transaction writes it, so no two can overlap. If you need the number, derive it from events off-chain or shard it.

solidity// Serialises everything
uint256 public totalMints;
function mint() external { totalMints++; ... }

// Parallel-friendly: per-user state, count off-chain from Mint events
mapping(address => uint256) public minted;
event Mint(address indexed by, uint256 id);
function mint() external { minted[msg.sender]++; emit Mint(msg.sender, ...); }

Pattern 2: Partition by actor

Mappings keyed by msg.sender, by token ID, by market, or by pool put unrelated activity in unrelated slots. An ERC-20 transfer touches two balances; thousands of transfers between different pairs are fully independent.

Pattern 3: Keep aggregates lazy

Where a running total is truly needed on-chain (total supply, total staked), accept that writes to it serialise, and keep it off the hottest path. Mint and burn update supply; transfer does not need to touch it.

Pattern 4: Beware the shared array

push writes the length slot. Every push conflicts with every other push. Prefer mappings with a per-key index, or emit an event and let an indexer keep the list.

Pattern 5: External calls carry their callee's slots

If every function calls into one router contract that updates a shared fee accumulator, you have inherited that contract's contention. Look at the whole call graph, not just your own storage.

Measuring it

Guessing is unreliable. Monsmith's parallel profiler reads your contract, builds the dependency graph between functions and the storage slots they touch, scores each function from fully parallel to fully serial, and lists the specific variables causing conflicts. Run it before and after a refactor and the score tells you whether the change helped.

What not to do

  • Do not break correctness for parallelism. A conflicting transaction is re-executed, not lost.
  • Do not micro-optimise a contract with ten calls a day. Contention only matters under load.
  • Do not assume Ethereum gas tricks apply. Packing several values into one slot saves gas but concentrates writes on that slot.

Frequently asked questions

Does my Solidity need changes to run on Monad?
No changes are required for correctness. Changes to storage layout can increase how much of Monad's parallel throughput your contract benefits from.
What causes transactions to conflict on Monad?
A transaction conflicts when it reads a storage slot that an earlier transaction in the same block wrote. Global counters, shared arrays, and single aggregate variables are the usual causes.
How do I measure storage contention?
Use Monsmith's parallel profiler at monsmith.com/studio. It draws the dependency graph and scores each function.

Try it in Monsmith

Free, in the browser, no account. Compile, audit, profile, and deploy to Monad.

Open the studio