Write Python to read a CSV file line by line and compute a column aggregate without loading the entire file into memory.
Using the csv module with a generator or a running accumulator keeps memory use constant — O(1) space — regardless of file size. This matters when files are larger than available RAM, a common situation in data engineering pipelines.
How to think about it
The interviewer is checking two things: can you drive the csv module correctly, and do you grasp the memory difference between iterating row by row and loading the whole file at once. The load-bearing phrase is “without loading the entire file into memory” — that’s the cue to keep a running accumulator, not reach for pd.read_csv.
A worked example
A csv.DictReader wraps a file handle, which is itself a lazy iterator — so reading row by row holds exactly one row in memory at a time. (Here the file is a StringIO so the example is self-contained; the logic is identical against a real open().)
import csv
import io
from collections import defaultdict
CSV_DATA = """region,product,revenue
North,Widget,120.50
South,Gadget,340.00
North,Gadget,95.25
South,Widget,210.75
North,Widget,88.00
South,Gadget,150.50
"""
# Simple column sum — one row at a time, O(1) memory
def sum_column(fileobj, col):
total = 0.0
for row in csv.DictReader(fileobj):
total += float(row[col])
return total
print("Total revenue:", sum_column(io.StringIO(CSV_DATA), "revenue"))
# Group-by aggregate — still a single streaming pass
def sum_by_group(fileobj, group_col, value_col):
totals = defaultdict(float)
for row in csv.DictReader(fileobj):
totals[row[group_col]] += float(row[value_col])
return dict(totals)
print("By region:", sum_by_group(io.StringIO(CSV_DATA), "region", "revenue"))
print("By product:", sum_by_group(io.StringIO(CSV_DATA), "product", "revenue"))
Total revenue: 1005.0
By region: {'North': 303.75, 'South': 701.25}
By product: {'Widget': 419.25, 'Gadget': 585.75}
Both functions did their work in one streaming pass. sum_column carried a single running float; sum_by_group kept only one accumulator per group (a handful of keys), never the rows themselves. So the memory cost is set by the number of groups, not the size of the file — the same code totals a 1 KB file or a 1 TB one without change.
The real-file version
Against an actual file it’s the same loop inside a with block:
import csv
from collections import defaultdict
def sum_column(filepath: str, col: str) -> float:
total = 0.0
with open(filepath, newline="", encoding="utf-8") as fh:
for row in csv.DictReader(fh):
total += float(row[col])
return total
Two details earn their keep: newline="" lets the csv module handle line endings correctly across platforms (don’t pass "\n"), and the with block guarantees the file closes even if a row mid-stream raises.