LearnAI ToolsCareerPractice BuildsPlayContact
Lesson 2717 min read

Sorting Data

Learn how sort works in Perl, how to write custom numeric and string comparisons, and how to sort a hash by its values.

Introduction

sort is one of the most frequently used built-in functions in Perl, but its default behavior surprises many beginners: it sorts as strings, not numbers, unless you tell it otherwise. This lesson covers the sort block syntax, the special variables $a and $b, and how to sort more complex data like hashes.

What You Will Learn
  • Why sort(10, 2, 33) does not give you what you expect.
  • How to sort numerically with <=> and alphabetically with cmp.
  • How to write custom and reverse sort orders.
  • How to sort a hash by its values instead of its keys.

The Default sort Behavior

With no comparison block, sort compares its elements as strings, in standard ASCII/Unicode order. That is fine for words, but it produces confusing results for numbers, because "10" sorts before "2" as a string (since "1" comes before "2").

use strict;
use warnings;
my @numbers = (10, 2, 33, 4);
my @sorted = sort @numbers;
print "@sorted\n";
Output

Click Run to see what this code prints.

Watch Out

That output is not a bug - it is correct string sorting. If you want numeric order, you must say so explicitly.

Numeric Sorting with <=>

To sort numerically, provide a comparison block using the special package variables $a and $b, and the numeric "spaceship" operator <=>, which returns -1, 0, or 1.

use strict;
use warnings;
my @numbers = (10, 2, 33, 4);
my @sorted = sort { $a <=> $b } @numbers;
print "@sorted\n";
Output

Click Run to see what this code prints.

String Sorting with cmp

cmp is the string equivalent of <=>. The default sort already uses cmp semantics, but writing it explicitly makes the intent clear, and it is essential once you want case-insensitive sorting.

use strict;
use warnings;
my @words = ('banana', 'Apple', 'cherry');
my @default_sorted = sort @words;
my @ci_sorted = sort { lc($a) cmp lc($b) } @words;
print "Default: @default_sorted\n";
print "Case-insensitive: @ci_sorted\n";
Output

Click Run to see what this code prints.

Custom Sort Subroutines

For more complex or reusable comparisons, you can define a named subroutine instead of an inline block. Inside it, $a and $b are still available automatically.

use strict;
use warnings;
my @words = ('kiwi', 'apple', 'fig', 'watermelon');
sub by_length {
return length($a) <=> length($b);
}
my @sorted = sort by_length @words;
print "@sorted\n";
Output

Click Run to see what this code prints.

Sorting in Reverse

You can reverse a sort either by swapping $a and $b in the comparison, or by wrapping the whole sort in reverse. Swapping $a and $b is usually considered more idiomatic and avoids building an extra list.

use strict;
use warnings;
my @numbers = (2, 4, 10, 33);
my @desc = sort { $b <=> $a } @numbers;
print "@desc\n";
Output

Click Run to see what this code prints.

Sorting a Hash by Value

Hashes have no inherent order, so "sorting a hash" really means sorting its keys according to some rule about the values, then iterating in that order.

use strict;
use warnings;
my %scores = (Alice => 92, Bob => 78, Carlos => 85);
for my $name (sort { $scores{$b} <=> $scores{$a} } keys %scores) {
printf "%-10s %d\n", $name, $scores{$name};
}
Output

Click Run to see what this code prints.

The Schwartzian Transform

When the sort key is expensive to compute (a database lookup, a regex, a length calculation on a huge string), computing it once per element instead of once per comparison matters. The Schwartzian Transform does exactly that: map each element to [element, key], sort by the precomputed key, then map back to just the elements.

use strict;
use warnings;
my @words = ('watermelon', 'fig', 'apple', 'kiwi');
my @sorted = map { $_->[0] }
sort { $a->[1] <=> $b->[1] }
map { [$_, length($_)] } @words;
print "@sorted\n";
Output

Click Run to see what this code prints.

Common Mistakes

Avoid These Mistakes
  • Forgetting that plain sort compares as strings, producing "10, 2, 33" instead of numeric order.
  • Using cmp on numbers or <=> on strings - they do very different things.
  • Trying to declare $a and $b with my - they are special package globals sort relies on, and strict makes an exception for them.
  • Modifying $a or $b inside the sort block, which can produce unpredictable results.
  • Recomputing an expensive sort key on every comparison instead of using a Schwartzian Transform.

Best Practices

  • Always use { $a <=> $b } for numbers and { $a cmp $b } for strings - never rely on the default for numeric data.
  • Use lc($a) cmp lc($b) for case-insensitive string sorting.
  • Extract named sort subroutines when the comparison logic is reused or non-trivial.
  • Sort keys %hash { ... } when you need a hash processed in a specific order.
  • Reach for the Schwartzian Transform once your sort key is expensive to compute.

Frequently Asked Questions

Since Perl 5.8, sort is guaranteed stable - elements that compare equal keep their original relative order.

sort always returns a new list; to "sort in place" you reassign it back to the same array: @array = sort @array;.

It returns -1 if the left side is smaller, 0 if equal, and 1 if larger - exactly what sort needs to order elements.

Key Takeaways

  • Default sort compares as strings, which surprises numeric data.
  • Use <=> for numeric sorts and cmp for string sorts inside a sort block.
  • Swap $a and $b in the comparison to reverse the sort order.
  • Sorting a hash means sorting its keys() by some rule about the values.
  • The Schwartzian Transform avoids recomputing expensive sort keys repeatedly.

Summary

sort is simple on the surface but has real depth once numeric data, custom orders, and hashes get involved. With sorting covered, the next lesson moves to something every Perl script eventually needs: reading and writing text files.

Next Lesson →

Working with Text Files