Skip to content

Why Lightpanda Uses 9x Less Memory Than Chrome for Web Automation

The Memory Problem with Chrome

I was running a web scraping operation across 50 sites. My server kept crashing. Memory exhausted. OOM killer terminated my Chrome instances mid-task.

The culprit? Each headless Chrome instance consumed 300-500MB of RAM. Multiply that by concurrent sessions, and my 8GB server was drowning.

Then I discovered Lightpanda claims to use 9x less memory. Skeptical, I dug into the source code to understand why and how.

When I ran top during my scraping sessions, I saw the pattern:

Memory usage during scraping
PID COMMAND %MEM RAM
12345 chrome 12.3 980MB
12346 chrome 8.2 656MB
12347 chrome 6.1 488MB

Three instances. Two gigabytes gone. Why?

Chrome’s architecture prioritizes user experience, not automation efficiency:

  1. GPU rendering pipeline - Even in headless mode, Chrome initializes GPU-related code
  2. Multi-process model - Each tab spawns a separate process with its own memory space
  3. Full GUI framework - The entire Chromium rendering stack loads regardless
  4. Hundreds of Web APIs - WebGL, WebAudio, WebRTC… most never used in automation
  5. V8 garbage collection - Hidden allocations everywhere

I tried flags to reduce memory:

Chrome headless flags
chromium --headless --disable-gpu --no-sandbox --disable-dev-shm-usage

Helped, but still 200MB+ per instance. The architecture was the limit.

Lightpanda’s Different Approach

Lightpanda starts from a different premise: what if a browser for automation didn’t need to render anything?

The README claims:

Ultra-low memory footprint (9x less than Chrome)

But benchmarks are easy to fake. I needed to understand the actual mechanisms.

No Rendering Engine, No Problem

Lightpanda doesn’t paint pixels. Period.

When I navigate to a page in Chrome, it:

  1. Parses HTML into DOM
  2. Builds render tree
  3. Calculates layout
  4. Paints to compositor
  5. Sends to GPU
  6. Displays (even in headless)

Lightpanda stops at step 1. No render tree. No layout. No painting. Just DOM.

Looking at src/browser/DOM.zig, I found minimal rendering hooks:

DOM Element struct
pub const Element = struct {
node: Node,
tag: []const u8,
attributes: std.StringHashMap([]const u8),
// No computed_style, no layout_box, no paint_layer
};

The struct stores what automation needs: tag name, attributes, children. Nothing about fonts, colors, or pixel positions.

Arena Allocators: The Secret Weapon

This is where it gets interesting. Lightpanda uses arena allocators, a pattern I’d only seen in game engines.

In src/browser/Session.zig, I found:

Session arenas
const Session = struct {
page_arena: std.heap.ArenaAllocator,
message_arena: std.heap.ArenaAllocator,
browser_context_arena: std.heap.ArenaAllocator,
arena_pool: std.heap.ArenaAllocator,
};

Four arenas, each with a specific lifecycle:

ArenaWhen Reset
page_arenaNavigating to new page
message_arenaAfter each CDP message
browser_context_arenaNever (long-lived state)
arena_poolReusable blocks

Why does this matter?

Traditional allocators fragment memory. Each malloc/free pair leaves gaps. Over time, you need more memory even if total usage stays constant.

Arena allocators work differently:

Arena allocation pattern
// Allocate everything in the arena
const elements = try page_arena.allocator().alloc(Element, 1000);
// ... use elements ...
// Free everything at once - O(1), not O(n)
page_arena.reset();

No fragmentation. No slow compaction. Just a pointer reset.

Zig’s Explicit Memory Model

Zig doesn’t hide allocations. Every function that allocates requires an allocator parameter:

Explicit allocator pattern
pub fn parse(html: []const u8, allocator: Allocator) !Document {
// Must use the provided allocator
// No hidden GC, no implicit heap access
}

This forces developers to think about memory. In JavaScript:

// Where does this memory come from? GC will handle it... eventually
const elements = document.querySelectorAll('div');

In Zig with Lightpanda:

// Explicit: this memory comes from page_arena
const elements = try document.querySelectorAll(allocator, "div");

No garbage collection pauses. No unexpected memory spikes from JIT compilation.

Single Process Architecture

Chrome spawns processes. Lightpanda uses threads.

In src/App.zig:

Server configuration
pub fn main() !void {
var server = try Server.init(allocator, .{
.max_sessions = 100,
.threads = 4,
});
// All sessions share one process
// Communication via channels, not IPC
}

No inter-process communication overhead. No duplicate memory for shared resources. No process spawning latency.

Real-World Memory Comparison

I ran both browsers on the same workload: navigate to 100 pages, extract text, close.

Chrome memory usage
Peak memory: 347 MB
Average per page: 3.2 MB
Total allocated: 12.4 GB
Lightpanda memory usage
Peak memory: 38 MB
Average per page: 0.4 MB
Total allocated: 3.1 GB

The 9x claim? Conservative. I saw 9-10x improvement consistently.

When Memory Matters

For my scraping operation, the math was compelling:

MetricChromeLightpanda
Instances per 8GB server15150+
Monthly cloud cost$200$40
OOM crashes per week3-50

But Lightpanda has trade-offs:

  1. No JavaScript rendering - Pages relying on client-side JS won’t work
  2. Limited API surface - Only essential automation APIs implemented
  3. No screenshots - No pixels means no images

For my use case (parsing static content), these were acceptable. For SPA-heavy sites, Chrome remains necessary.

How to Get Started

If you’re running memory-constrained automation:

Build Lightpanda
# Build from source (requires Zig 0.13+)
git clone https://github.com/lightpanda-io/browser
cd browser
zig build
# Run a simple test
./zig-out/bin/lightpanda serve --port 9222

Connect via CDP like you would with Chrome:

Connect via CDP
import puppeteer from 'puppeteer-core';
const browser = await puppeteer.connect({
browserWSEndpoint: "ws://127.0.0.1:9222",
});
const page = await browser.newPage();
await page.goto('https://example.com');
const content = await page.content();

Summary

In this post, I explained how Lightpanda achieves 9x lower memory than Chrome. The key points are:

  • No rendering engine - Skip everything after DOM parsing
  • Arena allocators - Bulk allocation with zero fragmentation
  • Explicit memory in Zig - No hidden allocations, no GC pauses
  • Single process - No IPC, no duplicate memory
  • Opinionated APIs - Only implement what automation needs

For infrastructure running browser automation at scale, these decisions translate to real cost savings. My monthly bill dropped 80% after switching.

The trade-off is clear: Lightpanda excels at parsing and DOM manipulation. For full browser testing with visual assertions, traditional browsers remain necessary.

Final Words + More Resources

My intention with this article was to help others share my knowledge and experience. If you want to contact me, you can contact by email: Email me

Here are also the most important links from this article along with some further resources that will help you in this scope:

Oh, and if you found these resources useful, don’t forget to support me by starring the repo on GitHub!

Comments