Multidimensional Arrays

La Lakshmi Priya Updated 19 Oct 2026
7 min read ·Lesson 130 of 143

What Is a Multidimensional Array?

In simple terms: A multidimensional array is an array that contains other arrays. It lets you store data with more than one level — like a table with rows and columns, or a list of records where each record has several fields.

Technically: PHP arrays can hold values of any type, including arrays. When elements are themselves arrays, the structure becomes nested. Each level is accessed with its own pair of square brackets: $data[$row][$column]. There is no fixed limit on depth.

Why They Matter

Real data is rarely a flat list:

  • Tables: rows from a database, each with several columns
  • Grids: seat maps, game boards, spreadsheets
  • Nested records: an order with a customer and a list of items
  • Configuration: settings grouped by section
  • API responses: JSON documents with nested objects and lists

Three Common Shapes

1. A Grid (List of Lists)

<?php
$matrix = [
    [1, 2, 3],
    [4, 5, 6],
    [7, 8, 9],
];

echo $matrix[1][2] . "\n";   // row 1, column 2

foreach ($matrix as $row) {
    echo implode(' ', $row) . "\n";
}

$diagonalSum = 0;
for ($i = 0; $i < count($matrix); $i++) {
    $diagonalSum += $matrix[$i][$i];
}
echo "Diagonal sum: $diagonalSum\n";

Output

6
1 2 3
4 5 6
7 8 9
Diagonal sum: 15
  • $matrix[1] is the second row ([4, 5, 6]); [2] selects its third element (6).
  • The diagonal elements are where row and column index are equal: [0][0], [1][1], [2][2] → 1 + 5 + 9 = 15.

2. A Table of Records (List of Associative Arrays)

This is the most common shape in web applications — it is what a database query returns:

<?php
$users = [
    ['id' => 1, 'name' => 'Asha', 'city' => 'Chennai'],
    ['id' => 2, 'name' => 'Bala', 'city' => 'Madurai'],
    ['id' => 3, 'name' => 'Charu', 'city' => 'Coimbatore'],
];

echo $users[1]['name'] . "\n";

foreach ($users as $user) {
    echo "#{$user['id']} {$user['name']} — {$user['city']}\n";
}

Output

Bala
#1 Asha — Chennai
#2 Bala — Madurai
#3 Charu — Coimbatore

Each element of $users is a record; inside the loop, $user['name'] reads one field.

3. A Tree (Nested Associative Arrays)

<?php
$config = [
    'app' => ['name' => 'GoCourse', 'debug' => false],
    'database' => [
        'default' => 'mysql',
        'connections' => [
            'mysql' => ['host' => 'localhost', 'port' => 3306],
            'sqlite' => ['path' => '/data/app.sqlite'],
        ],
    ],
];

$driver = $config['database']['default'];
echo $config['database']['connections'][$driver]['host'] . "\n";
echo $config['database']['connections']['mysql']['port'] . "\n";

Output

localhost
3306

The path through the brackets mirrors the structure: database → connections → mysql → host. Note that a variable ($driver) can be used at any level.


Reading Nested Values Safely

Reading a missing key at any level raises a warning. Use ?? for optional data:

<?php
$order = ['id' => 501, 'customer' => ['name' => 'Asha']];

echo $order['customer']['address']['city'] ?? 'City not provided';
echo "\n";

Output

City not provided

?? checks the whole path without warnings, even though address does not exist. See Access Array Items for a path-based helper.


Adding and Updating Nested Data

<?php
$students = [
    ['name' => 'Asha', 'marks' => ['math' => 90, 'science' => 85]],
    ['name' => 'Bala', 'marks' => ['math' => 72, 'science' => 68]],
];

$students[0]['marks']['english'] = 78;                         // add a field to one record
$students[1]['marks']['science'] += 5;                         // update a nested value
$students[] = ['name' => 'Charu', 'marks' => ['math' => 95]];  // add a new record

echo count($students) . " students\n";
echo "Bala science: {$students[1]['marks']['science']}\n";
echo "Asha subjects: " . implode(', ', array_keys($students[0]['marks'])) . "\n";

Output

3 students
Bala science: 73
Asha subjects: math, science, english

Assigning to a nested key creates any missing levels automatically.


Looping Through Nested Data

A nested loop visits every value:

<?php
$students = [
    'Asha' => ['math' => 90, 'science' => 85, 'english' => 78],
    'Bala' => ['math' => 72, 'science' => 68, 'english' => 88],
    'Charu' => ['math' => 95, 'science' => 91, 'english' => 89],
];

foreach ($students as $name => $marks) {
    $average = array_sum($marks) / count($marks);
    printf("%-6s %6.2f\n", $name, $average);
}

echo "\nSubject averages:\n";
foreach (array_keys(reset($students)) as $subject) {
    $average = array_sum(array_column($students, $subject)) / count($students);
    printf("%-8s %6.2f\n", $subject, $average);
}

Output

Asha    84.33
Bala    76.00
Charu   91.67

Subject averages:
math      85.67
science   81.33
english   85.00

How It Works

  1. The outer loop goes through students; inner operations (array_sum(), count()) work on each student's marks.
  2. For subject averages, reset($students) returns the first student's marks, and array_keys() gives the subject names.
  3. array_column($students, $subject) extracts one subject's marks from every student — a column of the table.

array_column() is the key tool for reading "down" a column instead of "across" a row.


Counting Nested Elements

<?php
$students = [
    ['name' => 'Asha', 'marks' => [90, 85, 78]],
    ['name' => 'Bala', 'marks' => [72, 68, 88]],
    ['name' => 'Charu', 'marks' => [95, 91, 89]],
];

echo count($students) . "\n";
echo count($students, COUNT_RECURSIVE) . "\n";

Output

3
18
  • count() counts only the top level: 3 records.
  • COUNT_RECURSIVE counts every element at every level, including the inner arrays themselves: 3 records + 3 × (2 fields + 3 marks) = 18. This is rarely what you want for business logic; usually you count a specific level.

Rendering a Table of Records as HTML

<?php
$products = [
    ['sku' => 'PEN-01', 'name' => 'Gel Pen', 'price' => 20],
    ['sku' => 'NB-A5', 'name' => 'Notebook <A5>', 'price' => 60],
];

$headers = array_keys($products[0]);
?>
<table>
    <tr>
<?php foreach ($headers as $header): ?>
        <th><?= htmlspecialchars(ucfirst($header)) ?></th>
<?php endforeach; ?>
    </tr>
<?php foreach ($products as $product): ?>
    <tr>
<?php foreach ($product as $value): ?>
        <td><?= htmlspecialchars((string) $value) ?></td>
<?php endforeach; ?>
    </tr>
<?php endforeach; ?>
</table>

Output (HTML)

<table>
    <tr>
        <th>Sku</th>
        <th>Name</th>
        <th>Price</th>
    </tr>
    <tr>
        <td>PEN-01</td>
        <td>Gel Pen</td>
        <td>20</td>
    </tr>
    <tr>
        <td>NB-A5</td>
        <td>Notebook <A5></td>
        <td>60</td>
    </tr>
</table>

The header row comes from the keys of the first record, and nested foreach loops produce rows and cells. Every value is escaped with htmlspecialchars().


Practical Example: Sales Report by Region and Month

<?php
$sales = [
    'North' => ['Jan' => 120, 'Feb' => 135, 'Mar' => 150],
    'South' => ['Jan' => 200, 'Feb' => 180, 'Mar' => 210],
    'West'  => ['Jan' => 90,  'Feb' => 110, 'Mar' => 95],
];

$months = array_keys($sales['North']);
$monthTotals = array_fill_keys($months, 0);

printf("%-6s %5s %5s %5s %6s\n", 'Region', ...$months, ...['Total']);

foreach ($sales as $region => $byMonth) {
    foreach ($byMonth as $month => $amount) {
        $monthTotals[$month] += $amount;
    }

    printf("%-6s %5d %5d %5d %6d\n", $region, ...array_values($byMonth), ...[array_sum($byMonth)]);
}

printf("%-6s %5d %5d %5d %6d\n", 'Total', ...array_values($monthTotals), ...[array_sum($monthTotals)]);

Output

Region   Jan   Feb   Mar  Total
North    120   135   150    405
South    200   180   210    590
West      90   110    95    295
Total    410   425   455   1290

Step-by-Step Explanation

  1. The outer keys are regions; the inner keys are months. Each value is a sales figure (in thousands of rupees, for example).
  2. array_fill_keys($months, 0) creates a running total for each month.
  3. The nested loop adds every figure to its month's total, while array_sum($byMonth) gives each region's total (row total).
  4. The spread operator (...) passes array values as separate printf() arguments. Spreading an array literal (...['Total']) is used because, in a function call, a normal positional argument cannot follow an unpacked array.
  5. The final row prints the month totals and the grand total.

Row totals sum "across" the inner array; column totals require accumulating "down" through all outer elements.


When to Use Objects Instead

Deeply nested associative arrays become hard to manage: key typos are not detected, and nobody knows which keys a structure must contain. For important domain data (orders, invoices, users), classes with typed properties provide structure, validation, and editor support. Arrays remain ideal for simple tables, configuration, and data passing between layers. See the PHP OOP section.


Common Mistakes

Losing Track of the Level

Effect: $users['name'] instead of $users[0]['name'] — accessing a field on the list instead of on a record.

Fix: Name variables for what they hold ($users vs $user) and use var_dump() or print_r() to inspect structures.

Missing Keys in Nested Paths

Effect: Warnings, or errors when accessing offsets on null.

Fix: Use ?? for optional nested data.

Modifying the Loop Variable Instead of the Array

Effect: foreach ($users as $user) { $user['active'] = true; } changes nothing.

Fix: Write back with the key: $users[$i]['active'] = true;.

Inconsistent Record Shapes

Effect: Some records have keys that others lack, breaking loops and tables.

Fix: Normalize records when creating them, or use objects.


Best Practices

  • Use lists of associative arrays for tabular data, and nested associative arrays for grouped settings.
  • Access nested optional data with ??.
  • Use array_column() to work with columns of record lists.
  • Keep nesting shallow; extract inner structures into functions or classes when they grow.
  • Escape every value when rendering nested data as HTML.

Official reference: https://www.php.net/manual/en/language.types.array.php

0 Comments

Reviewed before they appear

No comments yet.

PHP
PHP Home PHP Switch PHP Match Alternative Syntax PHP Loops While Loop Do While Loop For Loop Foreach Loop Nested Loops Break Statement PHP Home PHP Intro PHP Install PHP Built-in Web Server PHP Syntax PHP Tags PHP and HTML PHP Comments PHP Multiline Comments PHP Variables Variables Scope PHP Casting Logical Operators Null Coalescing Operator Spaceship Operator Operator Precedence Continue Statement Modify Strings PHP If...Else...Elseif PHP If Modify Strings PHP If Operators PHP Functions Concatenate Strings PHP If...Else Slicing Strings User-defined Functions Slicing Strings PHP Shorthand if Escape Characters Default Argument Values PHP Nested if Pass by Reference String Interpolation Named Arguments PHP File Upload Variadic Functions Return Values Search in Strings Multibyte Strings Type Declarations in Functions PHP Numbers PHP Math PHP Floats PHP Random Numbers PHP Constants PHP Properties PHP Methods PHP Constructor Constructor Property Promotion PHP Cookies PHP Destructor PHP Access Modifiers Readonly Properties PHP Sessions PHP Inheritance Method Overriding PHP Filters The final Keyword CSRF Protection PHP Filters Advanced Session Security Secure Cookies PHP Magic Constants File Upload Security PHP Callback Functions PHP Operators Filesystem Security PHP JSON Arithmetic Operators Error Reporting in Production Hiding PHP Assignment Operators PHP Configuration (php.ini) Comparison Operators PHP Command Line (CLI) PHP Exceptions Increment / Decrement Operators Migrating to PHP 8 PHP Form Validation PHP Form Required PHP Form URL/E-mail Error Handling Form Input Sanitizing Escaping Output (htmlspecialchars) Custom Error Handler Checkbox, Radio and Select Inputs Sticky Forms PHP Form Complete PHP Date and Time Date Formatting Error Logging HTTP Headers and Redirects Output Buffering Sending Email PHP cURL Consuming REST APIs PHP Generators AJAX PHP AJAX Database AJAX XML AJAX Live Search AJAX Poll AJAX with JSON Security Introduction Password Hashing SQL Injection Prevention XSS Prevention Garbage Collection Project: Simple Blog PHP What is OOP PHP Classes/Objects The $this Keyword Access Array Items Update Array Items Add Array Items Remove Array Items Sorting Arrays Multidimensional Arrays Array Destructuring Array Spread Operator Search in Arrays Merge Arrays PHP Abstract Classes PHP Interfaces Polymorphism PHP Traits PHP Static Methods PHP Static Properties Late Static Binding Magic Methods Object Cloning
Ask about this post
AI Ask about this post

Ask questions about Multidimensional Arrays and get answers drawn from it.

Signed-in readers only.