Complex Data Structures
Learn how to build and access arrays of hashes, hashes of arrays, and hashes of hashes in Perl using arrow syntax.
Introduction
Real applications rarely deal with flat lists of scalars. A list of blog posts, each with tags and an author; a directory of employees grouped by department; a configuration file with nested sections — all of these are naturally nested data structures. Now that you understand references, you have everything you need to build them in Perl.
- How to build and access an array of hashes.
- How to build and access a hash of arrays.
- How to build and access a hash of hashes.
- How arrow syntax lets you navigate deeply nested data.
Arrays of Hashes
An array of hashes is one of the most common structures you will build — think of it as a table, where each element of the array is one "row" represented by a hash reference of column values.
use strict;use warnings;
my @employees = ( { name => 'Alice', role => 'Engineer', age => 29 }, { name => 'Bob', role => 'Designer', age => 34 }, { name => 'Carol', role => 'Manager', age => 41 },);
foreach my $employee (@employees) { print "$employee->{name} works as a $employee->{role}.\n";}
print "First employee's age: $employees[0]{age}\n";Click Run to see what this code prints.
Notice $employees[0]{age} — when the outer container is a plain array (not a reference), you can drop the initial arrow before the first subscript. This is one of Perl's arrow-elision shortcuts, covered in more detail below.
Hashes of Arrays
A hash of arrays is the reverse shape: each key maps to a list of values rather than a single scalar. This is perfect for grouping data, such as students enrolled per course.
use strict;use warnings;
my %courses = ( Perl => ['Alice', 'Bob'], Python => ['Carol', 'Dave', 'Eve'],);
foreach my $course (sort keys %courses) { my @students = @{$courses{$course}}; print "$course has " . scalar(@students) . " students: @students\n";}
push @{$courses{Perl}}, 'Frank';print "Perl now has: @{$courses{Perl}}\n";Click Run to see what this code prints.
Hash of Hashes
A hash of hashes maps each key to another hash of properties, which is a very natural way to represent records indexed by a unique identifier, such as a username or product code.
use strict;use warnings;
my %inventory = ( SKU101 => { name => 'Widget', price => 9.99, stock => 120 }, SKU102 => { name => 'Gadget', price => 24.50, stock => 35 },);
foreach my $sku (sort keys %inventory) { my $item = $inventory{$sku}; print "$sku: $item->{name} - \$$item->{price} ($item->{stock} in stock)\n";}Click Run to see what this code prints.
Arrow Syntax Shortcuts
Writing an arrow between every single subscript would get noisy fast, so Perl lets you drop the arrow between two consecutive subscripts (two square brackets, two curly braces, or one of each in a row). The first arrow, right after the variable name, is still required.
use strict;use warnings;
my $data = { company => 'Acme', teams => [ { name => 'Engineering', size => 12 }, { name => 'Sales', size => 7 }, ],};
print "Company: $data->{company}\n";print "First team name: $data->{teams}[0]{name}\n";print "Second team size: $data->{teams}->[1]->{size}\n";Click Run to see what this code prints.
Both $data->{teams}[0]{name} and the fully-arrowed $data->{teams}->[0]->{name} mean exactly the same thing — the shortcut is purely cosmetic, so use whichever reads clearer to you and your team.
Modifying Nested Data
You read and write deeply nested data the same way, using arrow syntax on the left-hand side of an assignment. Perl will even auto-create intermediate structures for you as needed, a behavior called autovivification.
use strict;use warnings;
my %inventory = ( SKU101 => { name => 'Widget', price => 9.99, stock => 120 },);
$inventory{SKU101}{stock} -= 5;print "New stock: $inventory{SKU101}{stock}\n";
# Autovivification: SKU103 did not exist, Perl creates it automatically$inventory{SKU103}{name} = 'Gizmo';$inventory{SKU103}{stock} = 50;print "New item: $inventory{SKU103}{name}, stock $inventory{SKU103}{stock}\n";Click Run to see what this code prints.
Common Mistakes
- Forgetting whether a nested level is an array or hash reference and using the wrong bracket type ([...] vs {...}).
- Accidentally relying on autovivification, which can silently create empty structures just by checking if a deep key exists.
- Trying to loop directly over a hash reference with foreach instead of dereferencing it first with keys %{$ref}.
- Losing track of how many levels deep a structure goes in a large, unfamiliar data dump — use Data::Dumper to inspect it.
- Mixing up @{$ref}[0] (array slice syntax) with $ref->[0] (single element access) — they look similar but behave differently.
Best Practices
- Sketch out the shape of a nested structure (on paper or in a comment) before writing code that builds it.
- Use Data::Dumper during development to print and verify the actual shape of complex structures: use Data::Dumper; print Dumper(\%inventory);
- Keep nesting to a reasonable depth (two or three levels); beyond that, consider modeling data with proper objects instead.
- Be intentional about autovivification — check with exists() first if accidentally creating a key would be a problem.
- Use consistent naming, like $ref suffixes or plural names for arrays, so it is clear at a glance what shape a variable holds.
Frequently Asked Questions
It is Perl automatically creating intermediate arrays or hashes when you assign to (or sometimes just access) a nested path that does not exist yet, so you do not have to manually initialize every level first.
Use exists(), and check each level carefully: exists $data->{teams} && exists $data->{teams}[0], since even exists() can autovivify intermediate levels in some cases.
Load the core Data::Dumper module and call Dumper() on a reference to your structure: use Data::Dumper; print Dumper($data);
Yes — an array of array references, like my @matrix = ([1,2,3], [4,5,6]);, works the same way and is useful for representing grids or matrices.
Key Takeaways
- An array of hashes models a list of records, each with named fields.
- A hash of arrays groups multiple values under each key.
- A hash of hashes models records indexed by a unique key, like a database table keyed by ID.
- Arrow syntax can be shortened between consecutive subscripts, but the first arrow after the variable is always required.
- Autovivification automatically creates missing intermediate structures when you assign into a nested path.
Summary
Nested data structures are how real Perl programs represent everything from configuration files to database results. Next, you will learn how to read that kind of data from, and write it to, actual files on disk.