The 7 Levels of Laravel Optimization: From Rookie to Optimization Overlord — with Benchmark — 98% reduction

Suman Shrestha
5 min readSep 11, 2024

--

Buckle up! We’re diving headfirst into the wild world of Laravel optimizations, climbing the treacherous ladder from “just glad it works” to “your colleagues might call you a wizard” or curse you for turning simple tasks into a dark art.

Main image

Level 1: The Rookie Move — Eager Loading

Ah, the basics. You just learned about eager loading, and you’re feeling unstoppable. “No more N+1 problems!” you shout. Two queries, everything loads, and you think you’ve cracked the code of the universe.

<?php
use App\Models\Post;
use Illuminate\Support\Facades\Route;

Route::get('/', function () {
$posts = Post::with('user')->get();
return view('posts', ['posts' => $posts]);
});

Result: 100K posts chugging along with 191MB of memory in 0.63 seconds. Resource cost? A not-so-cool 120(memory multiplied by time taken). You saved the day… kind of.

Level 1: Noob optimization

Level 2: The Wannabe Pro — “But Do You Really Need All That Data?

Now you’re questioning everything. “Do I need all those fields?” you ask, squinting like a detective in a crime movie. You decide to select only what’s absolutely necessary. Look at you, all grown up!

<?php
use App\Models\Post;
use Illuminate\Support\Facades\Route;

Route::get('/', function () {
$posts = Post::with('user:id,name')
->select('id','user_id','title')
->get();
return view('posts', ['posts' => $posts]);
});

Result: A 20% resource boost! You pat yourself on the back, feeling like you just saved the company millions. Spoiler: you didn’t. But hey, you’re getting there!

Level 3: The “Trust Me, I’m an Expert” Move — Separate the Queries, Baby!

You’re really showing off now. “Forget eager loading,” you say, “I’m going full manual!” You split those queries like a pro, grabbing posts and fetching users separately.

<?php
use App\Models\Post;
use App\Models\User;
use Illuminate\Support\Facades\Route;

Route::get('/', function () {
$posts = Post::select('id','user_id','title')->get();
$users = User::query()
->whereIn('id', $posts->pluck('user_id')->unique())
->pluck('name', 'id');
// in view: use $users[$post->user_id] to get the user name
return view('posts', ['posts' => $posts, 'users' => $users]);
});

Result: An extra 15% saved! You smugly sip your coffee, knowing no one can touch your newfound skills. This is called lookup table method.

Level 4: The “Why Isn’t This Working Better?” Moment

You’re trying something new — pushing your query ninja skills to the limit. Let the database handle distinct filtering! But wait, the results? Meh.

<?php
use App\Models\Post;
use App\Models\User;
use Illuminate\Support\Facades\Route;

Route::get(‘/’, function () {
$query = Post::query();
$posts = (clone $query)->select(‘id’, ‘user_id’, ‘title’)->get();
$users = User::query()
->whereIn(‘id’, (clone $query)->select(‘user_id’)->distinct())
->get();
return view(‘posts’, [‘posts’ => $posts, 'users' => $users]);
});

Result: Faster, but memory? Not so much. You shrug and tell yourself, “Optimization is a journey, not a destination.”. Surely time taken is down as I am not doing unique thingy in 100k rows.

Level 5: “Wait, I Might Actually Know What I’m Doing” Optimization

You’ve discovered toBase()—the secret sauce that skips Eloquent’s magic but keeps your data game strong. Now you’re on fire.

<?php
use App\Models\Post;
use App\Models\User;
use Illuminate\Support\Facades\Route;

Route::get(‘/’, function () {
$query = Post::query();
$posts = (clone $query)->select(‘id’, ‘user_id’, ‘title’)
->toBase()
->get();
$users = User::whereIn(‘id’, (clone $query)
->select(‘user_id’)->distinct())
->toBase()
->get();
return view(‘posts’, [‘posts’ => $posts, 'users' => $users]);
});

Result: Resource cost drops by 85%. Your confidence is through the roof. “I’m basically a Laravel guru, Taylor Otwell might contact me any time now” you think. Not quite, but you’re getting closer!

Level 6: The “Can’t Stop, Won’t Stop” Optimization

Now you’re chunking like a boss. You tell everyone in the office, “ChunkById is the future,” but no one knows what you’re talking about. Doesn’t matter, you’re still a genius in your own mind.


<?php
use App\Models\Post;
use App\Models\User;
use Illuminate\Support\Facades\Route;

Route::get(‘/’, function () {
$query = Post::query();
$posts = collect();

(clone $query)->select(‘id’, ‘user_id’, ‘title’)
->toBase()
->orderBy(‘posts.id’)
->chunkById(15000, function ($collection) use (&$posts) {
$posts->push(...$collection);
}, ‘id’);

$users = User::whereIn(‘id’, (clone $query)
->select(‘user_id’)
->distinct())
->toBase()
->get();

return view(‘posts’, [‘posts’ => $posts, 'users' => $users]);
});

Result: Boom! Another 14% optimised. You’re starting to think there’s nothing left to improve.

This is the best solution. Or is it?

Level 7: Boss Level — Your Colleagues Won’t Understand What You Just Did

This is it — the final form. You’ve mastered lazy loading, and now you’re on another plane of existence. While everyone else struggles with slow queries, you’re practically dancing with your efficient, memory-optimal code. I will just pass it as LazyCollection.

Note: It loads 10,000 records at once and keeps them in memory. Imagine it like chunkById and map function combined.


<?php
use App\Models\Post;
use App\Models\User;
use Illuminate\Support\Facades\Route;

Route::get('/', function () {
$query = Post::query();
$users = User::query()
->whereIn('id', (clone $query)
->select('user_id')->distinct())
->pluck('name', 'id');
$posts = (clone $query)
->select('id', 'user_id', 'title')
->toBase()
->lazyById(10000, 'id');

return view('welcome', [
'posts' => $posts,
'users' => $users,
]);
});

Result: Memory? What memory? You’ve unlocked the secret sauce of Laravel optimisation, and your colleagues stare in awe — or confusion. Either way, you’ve reached the top.

Note: The memory taken here will be the result of lazyById. So, try with different variation to find out at which point the resource cost will be lowest. For current scenario, 10K seems good. But if your query has too many column, you can lower it.

Congratulations! You’ve climbed the ranks from rookie to optimization overlord, squeezing every ounce of efficiency out of your code. You’ve got the skills, the swagger, and the benchmark of reducing resource cost from 120 to 2.4 (98%) to prove it. But remember, you started as the eager loader noob — stay humble!

And hey, don’t get too comfy — your boss is probably about to burst into your cubicle any second, asking, “Where’s the progress on the actual project, team?”

You’ve spent hours saving server time, but who’s gonna save you now?

Run Forest Run.

Follow me in https://twitter.com/@sumfreelancer

Connect me in linkedin at https://www.linkedin.com/in/suman-shresth/

--

--

Suman Shrestha
Suman Shrestha

Written by Suman Shrestha

#fullstack #tailwindcss #php #Laravel #Vue #React #Meroshare