Start learning free

LESSON 105 OF 415 · ARRAYS I

Closures and Sorting in Swift: sorted(by:) and Trailing Closures

Lesson Read the full explanation

A closure is a function without a name that you write inline and hand to another function. Nowhere is this more useful than sorting: sorted(by:) doesn't guess how you want things ordered — it asks you. You pass a closure that receives two elements and returns true when the first one should come before the second:

let nums = [3, 1, 2]
let small = nums.sorted(by: { (a: Int, b: Int) -> Bool in
    return a < b
})
// small is [1, 2, 3]

That works, but Swift lets you shrink it dramatically. The compiler already knows the closure's types, single-expression closures return implicitly, and $0, $1 are automatic names for the first and second parameter:

let small = nums.sorted(by: { $0 < $1 })

And when the closure is the last argument, you can drop it out of the parentheses entirely — trailing closure syntax:

let big = nums.sorted { $0 > $1 }   // [3, 2, 1]

The closure doesn't have to compare the elements themselves — any rule works. { $0.count > $1.count } orders strings by their length, longest first.


The trap: sort() is not sorted()

Swift has two sorting methods and beginners mix them up constantly. sorted() returns a NEW array and leaves the original alone. sort() rearranges the array in place — so it needs a var. Call it on a let constant:

let names = ["Charlie", "Al"]
names.sort()

...and the compiler stops you cold:

error: cannot use mutating member on immutable value: 'names' is a 'let' constant
1 | let names = ["Charlie", "Al"]
  | `- note: change 'let' to 'var' to make it mutable
2 | names.sort()
  |       `- error: cannot use mutating member on immutable value: 'names' is a 'let' constant

"Mutating member" means the method modifies the thing it's called on. When you see this error, either switch to sorted() (usually what you want) or declare the array with var.


Assignment
Follow the Coding Tutorial: create let names = ["Bo", "Alexandra", "Kim", "Charlie"], then

  • sort it alphabetically into ascending using the full form sorted(by: { $0 < $1 }) and print it,
  • sort it by length, longest first, into byLength using trailing closure syntax sorted { $0.count > $1.count } and print it.

The exact expected output is:

ascending = ["Alexandra", "Bo", "Charlie", "Kim"]
byLength = ["Alexandra", "Charlie", "Kim", "Bo"]

Hint
sorted never changes names, so both sorts start from the same original array — and printing an array with \(...) shows it in brackets, exactly like the expected output.


STEP 1 OF 5
    Ctrl + Enter to check

    SWIFT · ARRAYS I

    Closures And Sorting in Swift, explained

    What a closure is

    A closure is a function without a name that you write inline, between braces, and pass to another function. Sorting is the classic use. The method sorted(by:) does not decide the order itself. You give it a closure that takes two elements and returns true when the first should come before the second.

    let nums = [3, 1, 2]
    let small = nums.sorted(by: { (a: Int, b: Int) -> Bool in
        return a < b
    })
    print(small)
    
    [1, 2, 3]
    

    In the closure, (a: Int, b: Int) -> Bool lists the parameters and the return type, and in separates that header from the body.

    Shortening the closure

    Swift lets you drop most of that in three steps:

    1. Leave out the types. The compiler knows sorted(by:) compares two elements and returns a Bool.
    2. Leave out return. A closure with a single expression returns that expression.
    3. Use $0 and $1. These automatic names stand for the first and second parameter, so the header can go as well.
    let small = nums.sorted(by: { $0 < $1 })
    

    Trailing closure syntax

    When the closure is the last argument, you can write it after the parentheses. Here the call has no other arguments, so the parentheses disappear too:

    let big = nums.sorted { $0 > $1 }
    print(big)
    
    [3, 2, 1]
    

    The rule inside the closure can be anything that gives a yes or no answer for two elements. { $0.count > $1.count } puts longer strings first, because count is the length of a string.

    sorted() or sort()

    Swift has two sorting methods:

    • sorted() and sorted(by:) return a new array and leave the original untouched. They work on a let constant.
    • sort() and sort(by:) rearrange the array in place, so the array must be a var.

    Calling sort() on a constant is a compile error:

    let names = ["Charlie", "Al"]
    names.sort()
    
    error: cannot use mutating member on immutable value: 'names' is a 'let' constant
    

    "Mutating member" means the method modifies the value it is called on. Either switch to sorted(), which is usually what you want, or declare the array with var.

    The exercise, step by step

    1. Create names as a let array of four strings. A constant is enough, because sorted never changes it.
    2. Sort alphabetically into ascending with sorted(by: { $0 < $1 }). Strings compare in alphabetical order with <, so "Alexandra" comes first and "Kim" last.
    3. Print ascending with string interpolation, \(...), which inserts any value, an array included, into the string.
    4. Sort by length into byLength with a trailing closure that returns true when the first string is longer.
    5. Print byLength the same way.
    let names = ["Bo", "Alexandra", "Kim", "Charlie"]
    let ascending = names.sorted(by: { $0 < $1 })
    print("ascending = \(ascending)")
    let byLength = names.sorted { $0.count > $1.count }
    print("byLength = \(byLength)")
    
    ascending = ["Alexandra", "Bo", "Charlie", "Kim"]
    byLength = ["Alexandra", "Charlie", "Kim", "Bo"]
    

    Both results come from the same names array, which keeps its original order. The only difference between them is the closure.

    Common mistakes

    • Using sort() on a let. Use sorted() or a var.
    • Ignoring the result of sorted. It returns the new array. Writing names.sorted() on its own line changes nothing.
    • Getting the direction backwards. The closure answers "should the first come before the second?". < gives ascending order and > gives descending.
    • Expecting the shortest first. $0.count > $1.count puts the longest first. Use < for the opposite.

    Key takeaways

    • A closure is an unnamed function you pass as an argument.
    • sorted(by:) returns a new array ordered by your rule, and sort(by:) reorders a var array in place.
    • $0 and $1 name the closure's parameters, and a last-argument closure can go outside the parentheses.
    Keep going with SwiftAbout 340 step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free