Originally published on php-to-js.learnio.dev — part of the PHP to JavaScript course. View this lesson on the original site.

Fail fast at the top so the happy path stays flat

Deep if nesting is hard to review in PHP controllers and in JavaScript handlers. Guard clauses handle invalid cases first, then leave one clear main path.

PHP habit: pyramid of doom

<?php
function checkoutLabel(?array $cart, bool $loggedIn): string {
    if ($cart !== null) {
        if ($loggedIn) {
            if (count($cart['items'] ?? []) > 0) {
                return 'Ready to pay';
            }
            return 'Cart is empty';
        }
        return 'Please log in';
    }
    return 'No cart';
}

Each extra level pushes the success case further right. In review you chase indentation to find what actually runs.

JavaScript equivalent: guards first

function checkoutLabel(cart, loggedIn) {
  if (!cart) return 'No cart';
  if (!loggedIn) return 'Please log in';
  if ((cart.items ?? []).length === 0) return 'Cart is empty';
  return 'Ready to pay';
}
Pattern PHP JavaScript
Missing input if ($x === null) return ... if (!x) return ...
Permission if (!$user->can(...)) throw ... if (!allowed) return ...
Invalid state throw ValidationException return or throw

Guards are not a different language feature — they are a style: handle the boring cases and exit.

When to return vs throw

In PHP you often throw for impossible states in domain code. In browser JavaScript you might return an error string for UI, or throw when the caller should catch. Pick one convention per layer and stick to it.

flowchart TD
  A[Enter function] --> B{Valid input?}
  B -->|no| C[Guard: return or throw]
  B -->|yes| D{Authorised?}
  D -->|no| C
  D -->|yes| E[Main path: one indentation level]

Failure mode: guards that hide bugs

if (!cart) return treats 0 and '' as missing when you only meant null. Be explicit when the type matters: if (cart == null).

Try it

Use the amber Try it panel below. Click Run.

const samples = [
  [null, true],
  [{ items: ['book'] }, false],
  [{ items: [] }, true],
  [{ items: ['book'] }, true],
];

for (const [cart, loggedIn] of samples) {
  console.log(checkoutLabel(cart, loggedIn));
}

You should see four distinct messages with no nested if in the function body.

From PHP to JavaScript — Control Flow, Functions, and Scope