Laravel chunk() vs cursor() vs lazy() — Handle Large Data Without Crashing Your Server
Source: Dev.to

Introduction
If you’ve ever tried to process thousands of rows in Laravel and got a memory error or server timeout — this article is for you. I learned this the hard way when a large CSV export caused Apache timeout errors in production. Here’s what I found out.
The Problem
// ❌ This will crash on large tables
$users = User::all();
foreach ($users as $user) {
// process...
}
all() loads every row into memory at once. On 100,000+ rows, your server will run out of memory.
chunk() — Process in Batches
User::chunk(500, function ($users) {
foreach ($users as $user) {
// processes 500 rows at a time
}
});
- ✅ Memory stays low
- ✅ Good for background jobs
- ⚠️ Don’t modify/delete rows inside
chunk()— it can skip records - ⚠️ Runs multiple SQL queries
Use when: sending emails in batches, background processing.
chunkById() — Safer Version of chunk()
User::chunkById(500, function ($users) {
foreach ($users as $user) {
// safe even when updating rows
}
});
- ✅ Safe to update/delete rows inside
- ✅ More reliable than
chunk()
Use when: updating or deleting large amounts of records.
cursor() — One Row at a Time
foreach (User::cursor() as $user) {
// processes one row at a time
}
- ✅ Most memory efficient
- ✅ Only one SQL query
- ❌ Can’t use eager loading (
with()) - ❌ Keeps DB connection open the whole time
Use when: read‑only processing, CSV exports.
lazy() — Best of Both Worlds
foreach (User::lazy() as $user) {
// chunks behind the scenes, feels like cursor()
}
- ✅ Memory efficient
- ✅ Supports eager loading with
with() - ✅ Cleaner syntax
User::with('orders')->lazy()->each(function ($user) {
// process
});
Use when: you need cursor()‑style iteration and need relationships loaded.
Quick Comparison Table
| Method | Memory | SQL Queries | Supports with() | Safe to modify? |
|---|---|---|---|---|
all() | ❌ High | 1 | ✅ | ✅ |
chunk() | ✅ Low | Multiple | ✅ | ⚠️ No |
chunkById() | ✅ Low | Multiple | ✅ | ✅ Yes |
cursor() | ✅ Lowest | 1 | ❌ | ✅ |
lazy() | ✅ Low | Multiple | ✅ | ✅ |
My Real‑World Example
This fixed my Apache timeout issue on CSV export:
// ❌ Before — caused timeout
$users = User::all();
// ✅ After — works perfectly
foreach (User::cursor() as $user) {
fputcsv($handle, [
$user->name,
$user->email,
$user->created_at
]);
}
Summary
chunk()— batch jobs, email sendingchunkById()— when modifying data in batchescursor()— read‑only, CSV exports, most memory efficientlazy()— when you needcursor()+ relationships
Want to dive deeper? Check out the full lesson on my free Laravel tutorial site:
https://php-laravel-tutorials.netlify.app/lesson-laravel-large-data