DEV Community

Felicia Laurent
Felicia Laurent

Posted on

How Layer 2 Rollups Work: A Developer's Technical Breakdown with Code Examples published

If you're doing layer-2 blockchain development on Ethereum or an EVM-compatible chain, you've probably run into gas costs and throughput limits that reduce how efficiently certain applications can run directly on Layer 1. Rollups are the most widely adopted answer to that problem, and understanding how they work at the code level makes the trade-offs a lot more concrete than reading about them in the abstract.

The Problem Rollups Solve

Layer 1 validates every transaction with every node. That's the security model, but it also means throughput is capped by what the slowest node in consensus can keep up with. Rollups move execution off-chain and only post compressed data (and a validity proof) back to Layer 1.

How a Rollup Batches Transactions

At a basic level, a rollup sequencer collects a batch of transactions, executes them off-chain, and submits a single transaction to Layer 1 containing:

  • The compressed transaction data (so it can be reconstructed if needed)
  • A new state root representing the result of the batch
  • A proof (fraud proof for optimistic, validity proof for zero-knowledge)

Here's a simplified pseudocode version of what a rollup contract checks on Layer 1:

function submitBatch(bytes calldata batchData, bytes32 newStateRoot, bytes calldata proof) external {
require(verifyProof(batchData, newStateRoot, proof), "Invalid proof");
require(newStateRoot != currentStateRoot, "No state change");

currentStateRoot = newStateRoot;
emit BatchSubmitted(batchData, newStateRoot);
Enter fullscreen mode Exit fullscreen mode

}

The complexity lives almost entirely in verifyProof() that's where optimistic and zero-knowledge rollups diverge.

Optimistic vs Zero-Knowledge, in Code

Optimistic rollups don't verify anything at submission time. Instead, they open a challenge window:
function challengeBatch(uint256 batchId, bytes calldata fraudProof) external {
require(block.timestamp < batches[batchId].challengeDeadline, "Window closed");
require(verifyFraudProof(batchId, fraudProof), "Fraud proof invalid");

revertBatch(batchId);
Enter fullscreen mode Exit fullscreen mode

}

If nobody challenges within the window, the batch is treated as final. This is cheap on-chain but means withdrawals need to wait out the challenge period.

Zero-knowledge rollups verify a cryptographic proof at submission time, so there's no challenge window needed:
function submitBatch(bytes calldata batchData, bytes32 newStateRoot, bytes calldata zkProof) external {
require(zkVerifier.verify(zkProof, newStateRoot), "ZK proof invalid");
currentStateRoot = newStateRoot;
}

The trade-off shows up in gas cost and compute: generating a zero-knowledge proof is expensive off-chain, but verifying it on-chain is cheap and immediate.

Practical Notes for Implementation

A few things worth knowing before choosing a rollup approach for a project:

Optimistic rollups are generally simpler to build and audit, which matters for smaller teams.

Zero-knowledge rollups suit applications where fast withdrawal finality matters more than development simplicity.

Bridge contracts (moving assets between Layer 1 and Layer 2) are consistently where security audits find the most issues; this is worth extra review time regardless of which rollup type is used.

Most production Blockchain Development teams don't build a rollup from scratch; they build on top of existing rollup frameworks and focus engineering effort on the application layer instead. Understanding the underlying mechanics still matters, though, since it directly affects how you design withdrawal flows and estimate gas costs for users.

Top comments (0)