Back to Blog
March 14, 2025

Why Solana transactions run sequentially -- and how to fix it

Solana's Sealevel runtime was built for parallel execution. But the default developer experience is sequential. We built an execution engine that changes that.

The gap between runtime and reality

Solana's Sealevel runtime is one of the few blockchain VMs that natively supports parallel transaction execution. If two transactions touch different accounts, they can run at the same time across different cores. This is Solana's fundamental throughput advantage over sequential VMs like the EVM.

But here's the problem: almost nobody uses it.

When a developer builds a multi-step operation -- say, swapping USDC to SOL, then staking that SOL, then providing liquidity with the remainder -- they typically construct a single transaction with sequential instructions. Instruction 1 runs, then instruction 2, then instruction 3. Even when some of those instructions are completely independent and could run in parallel.

The runtime can parallelize. The developer doesn't. Not because they're lazy, but because there's no tooling to do it. You'd have to manually analyze which accounts each instruction reads and writes, determine which instructions are independent, split them into separate transactions, and submit them concurrently. For every operation. Every time.

What "parallel execution" actually means

Two Solana transactions can execute in parallel if and only if they don't conflict on account access. Specifically:

This means parallelism is determined entirely by the account access patterns of your instructions. If you can figure out which instructions are independent -- which ones don't share any write-accessed accounts -- you can run them in parallel.

This is a graph problem.

Modeling transactions as a DAG

IVZA models multi-step operations as a directed acyclic graph (DAG). Each instruction becomes a node. Each data dependency becomes an edge.

Consider a simple example: swap USDC to SOL, then stake the SOL. The swap instruction writes to the user's SOL account. The stake instruction reads from the same SOL account. That's a read-write conflict, so there must be an edge from swap to stake. They can't be parallel.

But now add a third instruction: create an associated token account for a different token. This instruction doesn't touch the SOL account at all. There's no conflict with either swap or stake. So it can run in parallel with both.

The engine's DependencyAnalyzer scans every pair of nodes, checks their account access sets for overlaps, and inserts edges only where true conflicts exist. What comes out is a DAG that represents exactly which operations depend on which others -- and, critically, which ones don't.

dependency_check.rs
let analyzer = DependencyAnalyzer::new();
let graph = analyzer.analyze(&nodes);

// Only nodes with actual account conflicts
// get dependency edges. Everything else is
// free to execute in parallel.
for edge in &graph.edges {
    println!(
        "{} -> {} ({})",
        edge.from, edge.to, edge.dependency_type
    );
}

From graph to parallel lanes

Once you have the dependency graph, the next step is scheduling. The goal: pack as many independent nodes as possible into parallel execution lanes.

IVZA uses a topological sort to determine execution levels. Nodes with no incoming edges go in level 0 (they can all run in parallel). Nodes that depend only on level-0 nodes go in level 1. And so on. Within each level, nodes are assigned to lanes using greedy bin-packing with conflict checking -- a node can only join a lane if it doesn't conflict with any other node already in that lane.

Before scheduling, the engine runs a critical path analysis (CPM). This identifies the longest chain of dependent nodes through the graph -- the bottleneck that sets the minimum execution time. Nodes on the critical path get highest priority during scheduling. Nodes with slack (flexibility in when they run) are scheduled around them.

The result is an ExecutionPlan: an ordered set of lanes, where each lane contains a set of non-conflicting transactions that can execute simultaneously.

Execution on Sealevel

Each lane maps to either a Jito bundle or a batch of transactions submitted concurrently. Lanes are independent by construction -- no lane contains a transaction that conflicts with a transaction in another lane at the same level.

This is where Sealevel does what it was designed to do. The runtime sees transactions accessing different account sets and executes them across available cores. No wasted cycles. No unnecessary sequentiality.

execute.ts
const client = new IvzaClient(connection, wallet);

const plan = await client.processIntent(`
  swap 500 USDC to SOL
  stake 50% of output SOL
  create ATA for wSOL
`);

// plan.lanes[0]: [swap, create_ata]  -- parallel
// plan.lanes[1]: [stake]             -- sequential (depends on swap)
const results = await client.executeLocally(plan);

Not another chain

Projects like Monad, Sei, and MegaETH are building entirely new blockchains to achieve parallel execution. That's a valid approach, but it comes with a cost: new chain means new ecosystem, new liquidity, new trust assumptions, new tooling.

IVZA takes a different approach. It's a middleware layer that sits between your application and Solana. Same chain. Same liquidity. Same composability. The only difference is that your transactions are now structured for maximum parallelism instead of default sequentiality.

The entire engine -- graph decomposition, dependency analysis, critical path scheduling, route optimization -- is open source and MIT licensed. It's written in Rust with a TypeScript SDK.

Read the source on GitHub.