Skip to content

Working with large workbooks

Large workbook pipelines fail when they treat an .xlsx file as a small in-memory object graph. A multi-hundred-MB workbook is a ZIP of XML parts. Loading the whole file into a DOM, then materializing Python workbook, worksheet, row, cell, string, style, and relationship objects, can multiply memory far beyond the compressed file size.

WolfXL's fast path is to keep the workflow narrow: stream values when reading, batch rows when writing, and avoid materializing one Python Cell object per coordinate unless you need cell-level metadata or mutation.

Read large files with streaming values

Use read-only mode for ingestion jobs that scan rows and do not need to mutate the workbook. The public entry point is load_workbook(path, read_only=True), and row iteration is ws.iter_rows(values_only=True).

from wolfxl import load_workbook

wb = load_workbook("input.xlsx", read_only=True, data_only=True)
ws = wb.active

for row in ws.iter_rows(values_only=True):
    customer_id, invoice_date, amount = row[:3]
    process_invoice(customer_id, invoice_date, amount)

On the dated 2026-06-10 supported-scope benchmark, read-only-mode reads were 12.7x faster than openpyxl 3.1.5 at 200,000-row scale. The ws.iter_rows(values_only=True) path was 18.0x faster than openpyxl 3.1.5 on the same supported-scope baseline. Smaller grids and workbooks that need cell objects can show smaller multipliers.

Use values_only=True when you only need scalar values. It avoids creating a Python Cell object for every coordinate, which is the main source of memory and dispatch overhead in large row scans.

Write large files with batch APIs

For exports, build rows as Python lists and hand them to WolfXL in row batches. ws.append(row) is the openpyxl-compatible append path. ws.write_rows(grid, start_row=..., start_col=...) writes a 2D grid at an arbitrary position.

from wolfxl import Workbook

wb = Workbook()
ws = wb.active
ws.append(["customer_id", "invoice_date", "amount"])

for invoice in invoices:
    ws.append([invoice.customer_id, invoice.date, invoice.amount])

wb.save("invoices.xlsx")

For larger batches where you already own the data grid, write the grid directly:

from wolfxl import Workbook

wb = Workbook()
ws = wb.active

rows = [
    ["customer_id", "invoice_date", "amount"],
    *(
        [invoice.customer_id, invoice.date, invoice.amount]
        for invoice in invoices
    ),
]

ws.write_rows(rows, start_row=1, start_col=1)
wb.save("invoices.xlsx")

On the dated 2026-06-10 supported-scope benchmark, WolfXL's batch write paths were 8-9x faster than openpyxl 3.1.5 at 200,000-row scale. This claim is for supported-scope batch writes, not arbitrary cell-by-cell mutation workloads.

Avoid per-cell assignment for bulk export:

# Avoid this for large exports.
for row_index, row in enumerate(rows, start=1):
    for column_index, value in enumerate(row, start=1):
        ws.cell(row_index, column_index).value = value

Per-cell cell.value = ... mutation is a documented weak spot and can be slower than openpyxl. It remains useful for compatibility and small targeted edits, but batch APIs are the supported fast path for large writes.

Memory expectations

Expect lower peak memory when the job stays on the streaming value path. Published docs describe roughly 3x lower peak memory on large reads for the supported benchmark scope. That is a scoped expectation for large read pipelines that use streaming values, not a promise for arbitrary workbook shapes, API paths, or workflows that materialize cells, styles, comments, charts, pivots, or other metadata.

If your pipeline needs workbook-wide metadata or cell-level objects, measure that path on your own files. The memory profile is different from a value-only row scan.

Pipeline author checklist

  • Open ingestion jobs with load_workbook(path, read_only=True) when you do not need to save changes.
  • Iterate values with ws.iter_rows(values_only=True) for row scans.
  • Export with ws.append(row) or ws.write_rows(grid) instead of per-cell assignment.
  • Reserve cell.value = ... mutation for small targeted edits, not bulk writes.
  • Measure speed and peak RSS on representative files from your own workload before relying on any benchmark multiplier.