Skip to content

PHP Tutorial PHP Performance and Best Practices

Avoiding Repeated Work

Lesson 339 of 369 2 min read By Deepak Paswan Updated Oct 5, 2026

The fastest code is the code that does not run. Many slow pages repeat the same work again and again.

1. Move invariant work out of loops

index.php
PHP
<?php
$prices = [100, 250, 400];
$taxRate = 0.18;

// Repeats the same calculation on every pass
$slow = [];
foreach ($prices as $price) {
    $multiplier = 1 + (18 / 100);
    $slow[] = $price * $multiplier;
}

// Computed once
$multiplier = 1 + $taxRate;
$fast = array_map(fn($p) => $p * $multiplier, $prices);

echo $slow === $fast ? "same result" : "different";
Output
OUTPUT
same result

2. Remember results (memoization)

If a function is called many times with the same input, store the answer.

index.php
PHP
<?php
function fibonacci(int $n): int
{
    static $memo = [0 => 0, 1 => 1];

    return $memo[$n] ??= fibonacci($n - 1) + fibonacci($n - 2);
}

echo fibonacci(50);
Output
OUTPUT
12586269025

Without the memo, fibonacci(50) would make billions of calls. With it, each value is computed once.

3. Build a lookup instead of searching repeatedly

index.php
PHP
<?php
$orders = [["id" => 1, "user" => 7], ["id" => 2, "user" => 9], ["id" => 3, "user" => 7]];
$users = [["id" => 7, "name" => "Ana"], ["id" => 9, "name" => "Ben"]];

// Slow idea: search the users list for every order (a loop inside a loop)
// Fast idea: index the users by id once
$byId = array_column($users, "name", "id");

foreach ($orders as $order) {
    echo "Order {$order['id']}: {$byId[$order['user']]}\n";
}
Output
OUTPUT
Order 1: Ana
Order 2: Ben
Order 3: Ana

An array lookup by key is nearly instant. Searching a list with in_array() or a nested loop gets slower as the data grows.

4. The N+1 query problem

The classic performance bug. You run one query to get a list, then one more query per item.

index.php
PHP
<?php
$pdo = require __DIR__ . "/db.php";

$posts = $pdo->query("SELECT id, name AS title, category FROM products")->fetchAll();

$queries = 1;

// N+1: a query for each product's category count
foreach ($posts as $p) {
    $stmt = $pdo->prepare("SELECT COUNT(*) FROM products WHERE category = ?");
    $stmt->execute([$p["category"]]);
    $stmt->fetchColumn();
    $queries++;
}

echo "N+1 style: $queries queries\n";

// Better: ONE query for all the counts
$counts = $pdo->query("SELECT category, COUNT(*) AS n FROM products GROUP BY category")->fetchAll(PDO::FETCH_KEY_PAIR);

echo "Grouped: 2 queries in total, categories: " . count($counts);
Output
OUTPUT
N+1 style: 6 queries
Grouped: 2 queries in total, categories: 3

With five products it is a few queries. With 5,000 it is 5,001 queries, and the page crawls. Fix it with a JOIN, a GROUP BY or WHERE id IN (...) that fetches everything at once.

5. Batch remote calls

Do not call an API once per item if it has a bulk endpoint. Do not send one email per row inside a web request. Queue it, or send in one batch.

6. Do not compute what you will not show

Fetch only the columns and rows you need, calculate totals in SQL when possible, and paginate. A page that builds a list of 10,000 items to show 10 does 1,000 times more work than needed.

7. Reuse expensive objects

Create the database connection once per request and share it. Build a heavy object (a mailer, an HTTP client) once, not inside a loop.

8. Cache the results

If the answer does not change between requests, compute it once and store it: Caching Concepts.

A checklist

  • Is anything inside a loop that does not depend on the loop variable?
  • Is a query or API call inside a loop?
  • Am I searching the same list many times? Could an indexed array help?
  • Am I loading more than I show?
  • Could this be cached?
lightbulb
Count queries
A very effective habit is to count the database queries per page while you develop. Tools such as Query Monitor (WordPress), the Symfony profiler or Laravel Debugbar show it. A page with more than a few dozen queries usually has an N+1 problem.