0
0
Swiftprogramming~15 mins

Sorted and custom comparators in Swift - Deep Dive

Choose your learning style9 modes available
Overview - Sorted and custom comparators
What is it?
Sorting means arranging items in order, like putting books from shortest to tallest. In Swift, you can sort lists easily. Custom comparators let you decide exactly how to compare two items, so you can sort by any rule you want, not just simple order.
Why it matters
Without sorting, data can be messy and hard to find or use. Custom comparators let you sort things in ways that fit your needs, like sorting people by age or names by last letter. This makes programs smarter and more helpful.
Where it fits
Before learning sorting, you should know about arrays and basic data types. After sorting, you can learn about searching sorted data or more complex data structures like trees.
Mental Model
Core Idea
Sorting arranges items by comparing pairs, and custom comparators let you define your own comparison rules.
Think of it like...
Sorting with a custom comparator is like organizing a music playlist not by song title, but by how much you like each song, so you decide the order based on your feelings.
Array before sorting:
[5, 2, 9, 1]

Sorting process:
Compare 5 and 2 -> 2 is smaller
Compare 9 and 1 -> 1 is smaller
Rearranged array:
[1, 2, 5, 9]

With custom comparator (e.g., descending):
Compare 5 and 2 -> 5 is bigger
Compare 9 and 1 -> 9 is bigger
Rearranged array:
[9, 5, 2, 1]
Build-Up - 7 Steps
1
FoundationBasic array sorting in Swift
🤔
Concept: Learn how to sort an array of simple values using Swift's built-in sort method.
In Swift, you can sort an array of numbers by calling the sorted() method: let numbers = [5, 2, 9, 1] let sortedNumbers = numbers.sorted() print(sortedNumbers) // Output: [1, 2, 5, 9] This sorts numbers in ascending order by default.
Result
[1, 2, 5, 9]
Knowing the default sorting behavior helps you understand how Swift organizes data before customizing it.
2
FoundationUnderstanding the default comparator
🤔
Concept: Discover that Swift uses the less-than operator (<) to compare elements when sorting by default.
When you call sorted() without arguments, Swift compares elements using the < operator. For example, 2 < 5 is true, so 2 comes before 5. This works well for numbers and strings because Swift knows how to compare them naturally.
Result
Elements are arranged from smallest to largest using <.
Understanding the default comparison helps you see why sorting works automatically for many types.
3
IntermediateUsing a custom comparator closure
🤔Before reading on: do you think you can sort strings by their length using sorted()? Commit to yes or no.
Concept: Learn to pass a custom comparison function (closure) to sorted() to control sorting order.
You can tell Swift exactly how to compare two items by giving sorted() a closure: let words = ["apple", "banana", "pear"] let sortedByLength = words.sorted { $0.count < $1.count } print(sortedByLength) // Output: ["pear", "apple", "banana"] Here, the closure compares word lengths instead of alphabetical order.
Result
["pear", "apple", "banana"]
Custom comparators let you sort by any rule, not just natural order, making sorting flexible.
4
IntermediateSorting with multiple criteria
🤔Before reading on: can you sort people first by age, then by name if ages are equal? Commit to yes or no.
Concept: Combine multiple comparison rules in one comparator to sort by more than one property.
Suppose you have a list of people with name and age: struct Person { let name: String let age: Int } let people = [ Person(name: "Alice", age: 30), Person(name: "Bob", age: 25), Person(name: "Charlie", age: 30) ] Sort by age, then name: let sortedPeople = people.sorted { if $0.age == $1.age { return $0.name < $1.name } else { return $0.age < $1.age } } This sorts by age ascending, and if ages match, by name ascending.
Result
Bob (25), Alice (30), Charlie (30)
Using multiple criteria in comparators allows precise control over sorting complex data.
5
IntermediateSorting in descending order
🤔
Concept: Learn to reverse sorting order by changing the comparator logic.
To sort numbers from largest to smallest, reverse the comparison: let numbers = [5, 2, 9, 1] let descending = numbers.sorted { $0 > $1 } print(descending) // Output: [9, 5, 2, 1] Here, > means bigger comes first.
Result
[9, 5, 2, 1]
Changing the comparison operator flips the sorting order easily.
6
AdvancedSorting with custom types and Comparable
🤔Before reading on: do you think making your type conform to Comparable lets you use sorted() without a closure? Commit to yes or no.
Concept: Implement Comparable protocol to define default sorting for your custom types.
You can make your own type sortable by adding Comparable: struct Person: Comparable { let name: String let age: Int static func < (lhs: Person, rhs: Person) -> Bool { if lhs.age == rhs.age { return lhs.name < rhs.name } return lhs.age < rhs.age } } Now you can sort without a closure: let people = [Person(name: "Alice", age: 30), Person(name: "Bob", age: 25)] let sortedPeople = people.sorted() print(sortedPeople.map { $0.name }) // Output: ["Bob", "Alice"]
Result
["Bob", "Alice"]
Defining Comparable lets you reuse sorting logic everywhere without repeating closures.
7
ExpertPerformance and stability in custom sorting
🤔Before reading on: do you think Swift's sorted() always keeps the original order for equal elements? Commit to yes or no.
Concept: Understand that Swift's sorted() is stable and how comparator design affects performance and correctness.
Swift's sorted() uses a stable sort, meaning equal elements keep their original order. If your comparator is inconsistent (e.g., not a strict order), sorting can behave unpredictably. Also, complex comparators can slow sorting, so keep them efficient. Example of unstable comparator: let arr = [1, 2, 3] // Comparator that returns false for all comparisons let sorted = arr.sorted { _, _ in false } // Result may be unpredictable. Stable sorting helps when sorting by multiple criteria in steps.
Result
Sorted arrays keep equal elements in original order if comparator is consistent.
Knowing sorting stability and comparator rules prevents bugs and ensures predictable results.
Under the Hood
Swift's sorted() uses a stable sorting algorithm (like merge sort) that compares pairs of elements using the comparator you provide. It rearranges elements by repeatedly comparing and swapping them until the whole list is ordered. When you provide a custom comparator closure, Swift calls it many times to decide the order of each pair during sorting.
Why designed this way?
Stable sorting preserves the order of equal elements, which is useful when sorting by multiple criteria in steps. Using closures for comparators gives flexibility to define any sorting logic without changing the sorting algorithm itself. This design balances performance, flexibility, and ease of use.
Input Array
  │
  ▼
[Element1, Element2, Element3, ...]
  │
  ▼
Comparator Closure called many times:
  ┌─────────────┐
  │ Compare A & B│
  └─────┬───────┘
        │
        ▼
Sorting Algorithm (stable sort)
        │
        ▼
Sorted Array
  │
  ▼
[ElementX, ElementY, ElementZ, ...]
Myth Busters - 4 Common Misconceptions
Quick: Does sorted() change the original array or return a new one? Commit to your answer.
Common Belief:sorted() changes the original array in place.
Tap to reveal reality
Reality:sorted() returns a new sorted array and does not modify the original array. To sort in place, use sort().
Why it matters:Modifying the original array unexpectedly can cause bugs if other parts of code rely on the original order.
Quick: If two elements compare equal, does their order always stay the same after sorting? Commit to yes or no.
Common Belief:Sorting always keeps the original order of equal elements, no matter what.
Tap to reveal reality
Reality:Swift's sorted() is stable and keeps order for equal elements, but if your comparator is inconsistent or violates strict ordering rules, stability can break.
Why it matters:Assuming stability without a proper comparator can lead to unpredictable order and subtle bugs.
Quick: Can you use any function as a comparator, even if it sometimes returns inconsistent results? Commit to yes or no.
Common Belief:Any function that returns a Bool can be used as a comparator safely.
Tap to reveal reality
Reality:The comparator must define a strict, consistent order (transitive, antisymmetric). Otherwise, sorting behavior is undefined and may cause crashes or wrong results.
Why it matters:Using a bad comparator can cause your program to behave incorrectly or crash during sorting.
Quick: Does sorting always improve performance when searching? Commit to yes or no.
Common Belief:Sorting always makes searching faster.
Tap to reveal reality
Reality:Sorting helps only if you use search algorithms designed for sorted data, like binary search. Otherwise, sorting alone doesn't speed up searching.
Why it matters:Sorting without using proper search methods wastes time and resources.
Expert Zone
1
Custom comparators should be consistent and transitive to avoid undefined sorting behavior.
2
Using Comparable protocol for custom types centralizes sorting logic and improves code reuse.
3
Stable sorting allows multi-pass sorting strategies, sorting by secondary keys after primary keys.
When NOT to use
Avoid custom comparators when sorting very large datasets with complex logic that slows performance; consider pre-processing or indexing instead. For unsorted data where order doesn't matter, sorting is unnecessary overhead.
Production Patterns
In real apps, custom comparators are used to sort user lists by multiple fields, like last login date then username. Comparable conformance is common in model types to enable easy sorting. Stable sorting is leveraged when sorting tables with multiple columns.
Connections
Binary Search
Builds-on
Understanding sorting is essential before using binary search, which requires sorted data to quickly find items.
Database Indexing
Similar pattern
Sorting data in memory is like how databases create indexes to order data for fast retrieval.
Decision Making in Psychology
Analogous process
Custom comparators resemble how people prioritize choices based on personal criteria, showing sorting is a form of decision-making.
Common Pitfalls
#1Sorting an array but expecting the original array to change.
Wrong approach:let numbers = [3, 1, 2] numbers.sorted() print(numbers) // Still [3, 1, 2]
Correct approach:var numbers = [3, 1, 2] numbers.sort() print(numbers) // Now [1, 2, 3]
Root cause:Confusing sorted() which returns a new array with sort() which sorts in place.
#2Writing a comparator that is not consistent, causing unpredictable sorting.
Wrong approach:let arr = [1, 2, 3] arr.sorted { _, _ in false } // Always returns false
Correct approach:arr.sorted { $0 < $1 } // Proper consistent comparator
Root cause:Not following strict ordering rules in comparator leads to undefined behavior.
#3Sorting by multiple criteria incorrectly, ignoring equal cases.
Wrong approach:people.sorted { $0.age < $1.age } // Ignores name when ages equal
Correct approach:people.sorted { if $0.age == $1.age { return $0.name < $1.name } else { return $0.age < $1.age } }
Root cause:Not handling tie-breakers in comparator causes unstable or wrong order.
Key Takeaways
Sorting arranges data by comparing pairs, and Swift lets you customize this with comparator closures.
The default sorted() uses the less-than operator and returns a new sorted array without changing the original.
Custom comparators must be consistent and define a strict order to avoid unpredictable results.
Making your types conform to Comparable lets you reuse sorting logic easily across your code.
Swift's sorted() is stable, preserving order of equal elements, which is important for multi-criteria sorting.