Bird
Raised Fist0
Swiftprogramming~15 mins

Sorted and custom comparators in Swift - Deep Dive

Choose your learning style10 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.

Practice

(1/5)
1. What does the sorted(by:) method do in Swift?
easy
A. It sorts an array using a custom rule you provide.
B. It removes duplicate elements from an array.
C. It reverses the order of elements in an array.
D. It filters elements based on a condition.

Solution

  1. Step 1: Understand the purpose of sorted(by:)

    This method sorts elements of an array based on a closure that defines the order.
  2. Step 2: Compare with other array methods

    Unlike filtering or reversing, sorted(by:) changes order based on a custom rule.
  3. Final Answer:

    It sorts an array using a custom rule you provide. -> Option A
  4. Quick Check:

    sorted(by:) = custom sorting [OK]
Hint: Remember: sorted(by:) arranges items your way [OK]
Common Mistakes:
  • Confusing sorting with filtering
  • Thinking it removes duplicates
  • Assuming it reverses the array
2. Which of the following is the correct syntax to sort an array of integers in descending order using sorted(by:)?
easy
A. let sortedArray = numbers.sorted(by: { $1 > $0 })
B. let sortedArray = numbers.sorted(by: { $0 < $1 })
C. let sortedArray = numbers.sorted()
D. let sortedArray = numbers.sorted(by: { $0 > $1 })

Solution

  1. Step 1: Understand sorting order

    To sort descending, the comparator must return true when the first element is greater than the second.
  2. Step 2: Check each option

    let sortedArray = numbers.sorted(by: { $0 > $1 }) uses { $0 > $1 } which sorts descending. let sortedArray = numbers.sorted(by: { $0 < $1 }) sorts ascending. let sortedArray = numbers.sorted() sorts ascending by default. let sortedArray = numbers.sorted(by: { $1 > $0 }) reverses the comparison incorrectly.
  3. Final Answer:

    let sortedArray = numbers.sorted(by: { $0 > $1 }) -> Option D
  4. Quick Check:

    Descending sort uses $0 > $1 [OK]
Hint: Use $0 > $1 for descending sort [OK]
Common Mistakes:
  • Using $0 < $1 for descending
  • Forgetting to provide closure for custom order
  • Confusing parameter order in closure
3. What is the output of the following Swift code?
let names = ["Anna", "Bob", "Charlie"]
let sortedNames = names.sorted(by: { $0.count < $1.count })
print(sortedNames)
medium
A. ["Bob", "Charlie", "Anna"]
B. ["Bob", "Anna", "Charlie"]
C. ["Charlie", "Anna", "Bob"]
D. ["Anna", "Bob", "Charlie"]

Solution

  1. Step 1: Understand the sorting rule

    The closure sorts by string length ascending: shorter names come first.
  2. Step 2: Apply sorting to the array

    "Bob" has 3 letters, "Anna" 4, "Charlie" 7. So order is ["Bob", "Anna", "Charlie"].
  3. Final Answer:

    ["Bob", "Anna", "Charlie"] -> Option B
  4. Quick Check:

    Sort by length ascending = ["Bob", "Anna", "Charlie"] [OK]
Hint: Sort by $0.count < $1.count for shortest first [OK]
Common Mistakes:
  • Sorting alphabetically instead of by length
  • Confusing < with > in closure
  • Expecting original order
4. Identify the error in this Swift code that tries to sort an array of tuples by the second value:
let items = [("a", 3), ("b", 1), ("c", 2)]
let sortedItems = items.sorted(by: { $0.1 > $1.1 })
print(sortedItems)
medium
A. There is no error; the code sorts descending by second value.
B. The tuple elements cannot be accessed by index like $0.1.
C. The closure parameters should be $1.1 > $0.1 for ascending order.
D. The sorting closure must return an Int, not Bool.

Solution

  1. Step 1: Check tuple access syntax

    Accessing tuple elements by $0.1 and $1.1 is correct in Swift closures.
  2. Step 2: Verify sorting closure logic

    The closure returns true if first element's second value is greater, sorting descending by second value.
  3. Final Answer:

    There is no error; the code sorts descending by second value. -> Option A
  4. Quick Check:

    Tuple access and Bool return are correct [OK]
Hint: Tuple elements accessed as $0.1; closure returns Bool [OK]
Common Mistakes:
  • Thinking tuple elements can't be accessed by index
  • Expecting closure to return Int
  • Confusing ascending and descending order
5. You have an array of dictionaries representing people: let people = [["name": "Alice", "age": 30], ["name": "Bob", "age": 25], ["name": "Charlie", "age": 30]]. How do you sort this array first by age ascending, then by name ascending if ages are equal?
hard
A. people.sorted { $0["age"] as! Int < $1["age"] as! Int && $0["name"] as! String < $1["name"] as! String }
B. people.sorted(by: { $0["age"] as! Int > $1["age"] as! Int || $0["name"] as! String > $1["name"] as! String })
C. people.sorted { ($0["age"] as! Int, $0["name"] as! String) < ($1["age"] as! Int, $1["name"] as! String) }
D. people.sorted(by: { $0["name"] as! String < $1["name"] as! String })

Solution

  1. Step 1: Understand sorting by multiple keys

    Sorting by age ascending, then name ascending means comparing tuples of (age, name).
  2. Step 2: Check each option's logic

    people.sorted { ($0["age"] as! Int, $0["name"] as! String) < ($1["age"] as! Int, $1["name"] as! String) } compares tuples directly, which sorts by age first, then name if ages equal. people.sorted { $0["age"] as! Int < $1["age"] as! Int && $0["name"] as! String < $1["name"] as! String } uses && which fails for correct ordering. people.sorted(by: { $0["age"] as! Int > $1["age"] as! Int || $0["name"] as! String > $1["name"] as! String }) sorts descending and mixes conditions incorrectly. people.sorted(by: { $0["name"] as! String < $1["name"] as! String }) sorts only by name.
  3. Final Answer:

    people.sorted { ($0["age"] as! Int, $0["name"] as! String) < ($1["age"] as! Int, $1["name"] as! String) } -> Option C
  4. Quick Check:

    Tuple comparison sorts by multiple keys [OK]
Hint: Compare tuples (age, name) for multi-level sorting [OK]
Common Mistakes:
  • Using && instead of tuple comparison
  • Sorting only by one key
  • Mixing ascending and descending incorrectly
Recommended

Ready to prove it?

Pass the test and this pattern moves unknown strong while earning +1 Grip toward your certificate.

10 questions~5 mineasy hard