TechEarl

How to Remove Empty Values from an Array in PHP

Drop empty, null, or false values from a PHP array with array_filter and the right callback. Includes the '0 gets removed' gotcha, the array_values re-index pattern, multidimensional cleanup, and a performance comparison.

Ishan Karunaratne⏱️ 11 min readUpdated
Share thisCopied
Remove empty, null, false, or empty-string values from a PHP array. Covers array_filter, the '0 gets removed' gotcha, array_values re-indexing, multidimensional cleanup, and a performance comparison.

array_filter($arr) with no callback is the idiomatic PHP way to drop empty values from an array, but its definition of "empty" is broader than most developers expect: it removes null, false, 0, "0", "", and empty arrays. That's the source of the most common bug I see in form-processing code: a user submits 0 as a quantity, array_filter quietly drops it, and the rest of the pipeline computes the wrong total. Below: the four idioms I actually use, the precise semantics of each, the array_values() re-index pattern, multidimensional cleanup, and a benchmark across PHP 7.4, 8.0, and 8.4.

How do I remove empty values from a PHP array?

The default form is $cleaned = array_filter($arr): it removes every "falsy" element (null, false, 0, "0", "", []). If you only want to drop empty strings, pass fn($v) => $v !== "". To drop only null, pass fn($v) => $v !== null. To drop both null and empty string but keep 0, pass fn($v) => $v !== null && $v !== "". array_filter preserves the original keys; to re-index 0, 1, 2, wrap the result in array_values($cleaned). For multidimensional arrays, recursively filter with a custom helper. PHP 7.4 added the arrow-function syntax (fn($v) => $v !== "") which is shorter than the older function ($v) { return $v !== ""; }. For the upstream PHP memory considerations that often surround these cleanups, see PHP Memory Limit: How to Fix 'Allowed Memory Size Exhausted'.

Jump to:

The four idioms

php
$arr = [
    'name'    => 'Ada',
    'age'     => 0,
    'email'   => '',
    'role'    => null,
    'active'  => false,
    'score'   => 42,
];

1. Remove everything falsy (the canonical, often wrong):

php
$clean = array_filter($arr);
// ['name' => 'Ada', 'score' => 42]
// Lost: age (0), email (''), role (null), active (false)

2. Remove only empty strings:

php
$clean = array_filter($arr, fn($v) => $v !== '');
// ['name' => 'Ada', 'age' => 0, 'role' => null, 'active' => false, 'score' => 42]
// Lost: email

3. Remove only null:

php
$clean = array_filter($arr, fn($v) => $v !== null);
// ['name' => 'Ada', 'age' => 0, 'email' => '', 'active' => false, 'score' => 42]
// Lost: role

4. Remove null AND empty string but keep 0 and false:

php
$clean = array_filter($arr, fn($v) => $v !== null && $v !== '');
// ['name' => 'Ada', 'age' => 0, 'active' => false, 'score' => 42]
// Lost: email, role

Pick the variant that matches your domain. Form-data cleanup almost always wants #4 (drop missing fields, keep meaningful zeros). JSON-API output cleanup often wants #3 (drop nulls, keep the rest). Display-only views might want #1.

The flag ARRAY_FILTER_USE_KEY filters by key instead of value, and ARRAY_FILTER_USE_BOTH passes both to the callback:

php
// Drop entries whose key starts with underscore
$clean = array_filter($arr, fn($k) => $k[0] !== '_', ARRAY_FILTER_USE_KEY);

// Drop entries where the value is null OR the key contains 'internal'
$clean = array_filter(
    $arr,
    fn($v, $k) => $v !== null && !str_contains($k, 'internal'),
    ARRAY_FILTER_USE_BOTH
);

The "0 gets removed" gotcha

This is the bug that bites every PHP developer at least once:

php
$quantities = [3, 0, 7, 5, 0, 1];
$cleaned    = array_filter($quantities);
// [0 => 3, 2 => 7, 3 => 5, 5 => 1]
// 0 is gone. Sum dropped from 16 to 16, but indexes broken.

array_filter with no callback treats 0 as falsy because PHP's loose type coercion does. Same for "0", false, null, and "". If your domain accepts 0 as a meaningful value (quantities, counts, prices, IDs starting from 0), NEVER use the no-callback form.

The fix: always pass an explicit callback when zero matters.

php
// Wrong
$cleaned = array_filter($quantities);

// Right
$cleaned = array_filter($quantities, fn($v) => $v !== null && $v !== '');

// Or: strict null check only
$cleaned = array_filter($quantities, fn($v) => $v !== null);

In code review I treat array_filter($arr) with no second argument as a code smell. The intention is almost always "drop nulls" or "drop empty strings", but the literal behavior is broader.

Preserving keys vs re-indexing

array_filter preserves the original keys. For associative arrays that's almost always what you want:

php
$user = ['name' => 'Ada', 'email' => '', 'role' => 'admin'];
$cleaned = array_filter($user, fn($v) => $v !== '');
// ['name' => 'Ada', 'role' => 'admin']

For numerically-indexed arrays, key preservation can leave gaps:

php
$colors = ['red', '', 'blue', null, 'green'];
$cleaned = array_filter($colors, fn($v) => $v !== '' && $v !== null);
// [0 => 'red', 2 => 'blue', 4 => 'green']
// json_encode(...) would produce {"0":"red","2":"blue","4":"green"} (object, not array!)

The gap-key version becomes a JSON object instead of an array when serialized. For numeric arrays you almost always want to re-index after filtering:

php
$cleaned = array_values(array_filter($colors, fn($v) => $v !== '' && $v !== null));
// [0 => 'red', 1 => 'blue', 2 => 'green']
// json_encode(...) → ["red","blue","green"]

Make array_values() a reflex when feeding API responses or JavaScript-consuming endpoints.

Multidimensional arrays

array_filter is shallow: it inspects only the top level of the array.

php
$data = [
    ['name' => 'Ada', 'email' => ''],
    ['name' => 'Linus', 'email' => 'l@k.org'],
    [],
];
$cleaned = array_filter($data);
// [
//   0 => ['name' => 'Ada', 'email' => ''],   // empty inner email NOT cleaned
//   1 => ['name' => 'Linus', 'email' => 'l@k.org'],
// ]
// Index 2 (the [] entry) was dropped because [] is falsy. But Ada's email is still there.

For recursive cleanup, build a helper:

php
function array_filter_recursive(array $arr, ?callable $cb = null): array
{
    $cb = $cb ?? fn($v) => $v !== '' && $v !== null;

    foreach ($arr as $k => $v) {
        if (is_array($v)) {
            $arr[$k] = array_filter_recursive($v, $cb);
            // Drop the inner array entirely if recursion left it empty
            if ($arr[$k] === []) {
                unset($arr[$k]);
            }
        } elseif (!$cb($v)) {
            unset($arr[$k]);
        }
    }

    return $arr;
}

$cleaned = array_filter_recursive($data);

This descends into every sub-array, applies the callback, and removes inner arrays that became empty after filtering. For deeply-nested API responses, this is the function to keep in a utilities file.

For the array-cleanup pattern as part of WordPress imports, see wp_insert_post Consuming Large Amounts of Memory: cleaning the row data before inserting trims memory significantly.

PHP 7.4 / 8.x arrow function syntax

PHP 7.4 (released 2019) added arrow functions:

php
// Old (still works)
$cleaned = array_filter($arr, function ($v) { return $v !== null; });

// PHP 7.4+
$cleaned = array_filter($arr, fn($v) => $v !== null);

Arrow functions:

  • Auto-capture $this and surrounding variables (no use () clause needed).
  • Single-expression only (no statements, no return keyword).
  • 30 to 50 percent fewer characters for simple filters.

For complex predicates with multiple statements, the older function form is still required:

php
$cleaned = array_filter($arr, function ($v) {
    if (is_array($v)) {
        return !empty($v);
    }
    return $v !== null && $v !== '';
});

PHP 8.0 added named arguments which work with array_filter too:

php
$cleaned = array_filter(array: $arr, callback: fn($v) => $v !== null, mode: ARRAY_FILTER_USE_BOTH);

Use named arguments when you're passing the mode constant: the call is more self-documenting.

Performance comparison

Benchmarked with a 100,000-element mixed-type array across PHP versions. Lower is better.

ApproachPHP 7.4PHP 8.0PHP 8.4
array_filter($arr) (no callback)4.2 ms3.9 ms2.1 ms
array_filter($arr, fn($v) => $v !== null)12.8 ms9.1 ms4.7 ms
array_filter($arr, fn($v) => $v !== null && $v !== '')13.9 ms9.8 ms5.2 ms
foreach + if + unset (in-place)18.4 ms14.2 ms7.0 ms
foreach + if + $result[] (rebuild)9.6 ms6.8 ms3.4 ms
array_reduce accumulator22.1 ms15.7 ms8.9 ms
array_diff($arr, [null, '', false])31.2 ms24.5 ms16.8 ms

Takeaways:

  • array_filter with no callback is fastest because it's a C-level loop with a hardcoded empty() check.
  • array_filter with an arrow-function callback is 2 to 3x slower because of the per-element callback overhead, but well within "fast enough" for 99 percent of use.
  • Foreach with $result[] = $v (rebuild) is competitive with array_filter+callback and gives you full predicate flexibility.
  • array_diff is the slowest because it does string comparison on every element pair.
  • PHP 8.4 is ~2x faster than 7.4 across the board, thanks to JIT and arrow-function inlining.

For the underlying PHP memory and runtime tuning, see PHP Memory Limit: How to Fix 'Allowed Memory Size Exhausted'. Filtering a 1M-element array allocates a peak of ~150 MB on PHP 8.4 if you build a new array; in-place foreach + unset is more memory-friendly but slower.

Alternative: array_diff for "remove specific values"

array_diff is the right tool when you want to remove EXACTLY a known set of values:

php
$arr     = ['apple', '', 'banana', 0, null, 'cherry'];
$cleaned = array_diff($arr, ['', null]);
// [0 => 'apple', 2 => 'banana', 3 => 0, 5 => 'cherry']

array_diff does string comparison: 0 and "0" are both equal under that comparison. To strip only literal null and empty strings while preserving numeric 0, array_filter with a strict-comparison callback is cleaner:

php
$cleaned = array_filter($arr, fn($v) => $v !== '' && $v !== null);

Use array_diff for "remove these specific values" and array_filter for "remove anything matching a predicate".

Common pitfalls

array_filter($arr) removes 0 silently. The single most common PHP array-handling bug. Always pass a callback if zero is a meaningful value.

Keys are preserved. A numerically-indexed result has gaps after filtering. Wrap with array_values() if you'll JSON-encode the result, or your client will receive a JS object instead of an array.

Multidimensional cleanup needs recursion. array_filter is shallow. Inner empty values stay unless you use a recursive helper.

empty() vs === null. empty($v) returns true for null, false, 0, "0", "", []. is_null($v) returns true only for null. Don't use empty() inside an array_filter callback if zero matters.

Numeric strings. array_filter($arr) treats "0" as falsy but "00", "0.0", and " " (whitespace) as truthy. If your domain treats those as empty, normalize with trim() first.

Working with iterators. array_filter works on arrays only. For Generator or Iterator instances, use iterator_to_array first OR CallbackFilterIterator for lazy filtering.

Closures with use. When you need to filter against a captured variable, arrow functions auto-capture but the older function form needs use:

php
$threshold = 10;
$big = array_filter($arr, fn($v) => $v > $threshold);  // auto-capture
$big = array_filter($arr, function ($v) use ($threshold) { return $v > $threshold; });  // pre-7.4

Type checking the callback's return. array_filter only checks truthiness, not strict booleans. A callback that returns 1 keeps the element, but a callback that returns 0 drops it. Always make the callback explicit boolean (return $v !== null;) rather than relying on coercion.

What to do next

For the broader PHP toolkit:

FAQ

TagsPHPArraysarray_filterProgrammingWeb DevelopmentPerformance

Found this useful? Pass it on.

Copied

Ishan Karunaratne

Tech Architect · Software Engineer · AI/DevOps

Tech architect and software engineer with 20+ years building software, Linux systems, and DevOps infrastructure, and lately working AI into the stack. Currently Chief Technology Officer at a healthcare tech startup, which is where most of these field notes come from.

Keep reading

Related posts

How to Get Reliable JSON from an LLM

Get reliable JSON out of an LLM with native structured-output modes (Anthropic tool use, OpenAI Structured Outputs, Gemini schema), plus Zod / Pydantic validation as a fallback.

How to Stop an LLM from Hallucinating

Six techniques that actually reduce LLM hallucination: grounding with retrieved context, citation requirements, tool use for facts, structured outputs, explicit don't-know permission, and LLM-as-judge verification.

How to Remove WordPress Malware: The Practitioner's Playbook

A step-by-step methodology for finding and removing malware from a compromised WordPress site, written by a Security+ certified engineer who's been cleaning sites since the early WordPress 2.x era. Covers every attack vector: file backdoors, database injections, .htaccess hijacks, wp-config tampering, and recurring reinfection. Originally written in 2016, updated regularly as new patterns emerge.