Swift foundations · intro

Arrays, dictionaries and sets

The three collections you will use daily, and the transform methods that replace most hand-written loops.

12 min read16 min practice0/3 exercises5 recall cards

By the end you will be able to

  • Choose between Array, Dictionary and Set for a given job
  • Transform collections with map, filter, reduce, compactMap and sorted
  • Explain why collections are value types and what that means when you assign one
Guess firstAnswering before you read makes the explanation stick — even when you get it wrong.

What does this print?

var a = [1, 2, 3]
var b = a
b.append(4)
print(a.count)

Three collections, three jobs

TypeHoldsOrdered?Duplicates?Lookup cost
Arrayanythingyesyesby index: instant; by value: scans
Dictionarykey → valuenokeys uniqueby key: instant
Setanything hashablenonomembership: instant

Pick by the question you need to answer fast. "What is the third one?" → array. "What is the value for this key?" → dictionary. "Have I seen this before?" → set.

Arrays

var names = ["Ada", "Alan"]
names.append("Grace")
names.insert("Edsger", at: 0)
names.remove(at: 1)

names.count      // how many
names.isEmpty    // better than count == 0
names.first      // Optional — the array might be empty
names.last       // Optional
names[0]         // NOT optional — traps if out of range

first and last are optional; subscripting is not. That is deliberate: an empty array is an ordinary situation, whereas an index you computed wrongly is a bug you want to hear about immediately. Picture the refused alternative: if numbers[10] returned an optional, every ordinary access would grow ?? ceremony — and a genuinely wrong index would produce a nil that flows onward and surfaces as a blank label three screens away. The trap converts "quietly wrong somewhere downstream" into "loudly wrong on this exact line".

Dictionaries

var ages = ["Ada": 36, "Alan": 41]
ages["Grace"] = 45          // insert or replace
ages["Alan"] = nil          // remove
ages["Ada"]                 // Int?  — the key might be absent
ages["Nobody", default: 0]  // Int   — supply a fallback inline

The default: subscript is the neat solution to counting:

Swift

Sets

var seen: Set<String> = ["a", "b"]
seen.insert("c")
seen.contains("b")            // instant
seen.union(other)
seen.intersection(other)
seen.subtracting(other)

Note the type annotation. ["a", "b"] on its own is an array literal — you have to say Set<String> for Swift to build a set.

Swift

Transforms replace loops

This is the part that changes how your code reads.

let numbers = [1, 2, 3, 4, 5]

numbers.map { $0 * 2 }             // [2, 4, 6, 8, 10]     transform each
numbers.filter { $0 % 2 == 0 }     // [2, 4]               keep some
numbers.reduce(0, +)               // 15                   combine into one
numbers.contains { $0 > 4 }        // true
numbers.allSatisfy { $0 > 0 }      // true
numbers.first { $0 > 3 }           // Optional(4)
numbers.sorted { $0 > $1 }         // [5, 4, 3, 2, 1]

$0 is the first argument to the closure — shorthand you will see constantly. The next lesson covers closures properly; for now, read { $0 * 2 } as "given an element, produce twice it".

These compose, and each step names what it does:

Swift

compactMap — transform and drop the nils

["1", "two", "3"].compactMap { Int($0) }   // [1, 3]

map would give [Optional(1), nil, Optional(3)]. compactMap unwraps and discards, which is almost always what you wanted.

reduce — collapse to a single value

prices.reduce(0, +)                                  // sum
names.reduce("") { $0 + $1 }                         // concatenate
numbers.reduce(0) { max($0, $1) }                    // largest

The first argument is the starting value; the closure receives the running result and the next element.

Predict, then runA wrong prediction you have committed to is worth more than a right answer you read.

map and filter each build a new array. What does this print?

let source = [1, 2, 3, 4, 5, 6]
let result = source
    .filter { $0 % 2 == 0 }
    .map { $0 * $0 }
    .reduce(0, +)

print(result)
print(source)

Value semantics, one more time

Assigning a collection copies it. Passing one into a function copies it. This is the opposite of Java, Python, and JavaScript, and it removes a permanent source of "who else is holding a reference to this array?" bugs. The reference-world incident goes like this: a screen hands its items to a helper, the helper sorts its list to build a summary — and the screen's on-display order silently changes too, because there was only ever one list. In Swift that program is unwritable: the helper sorted its own copy.

The obvious worry is cost, and Swift handles it: collections are copy-on-write. The copy is deferred until one side actually mutates, so var b = a is cheap and only becomes a real copy if you write to it. You get value semantics at reference-assignment prices.

Swift
Check yourself

You need to check membership repeatedly against 50,000 stored ids while looping over 10,000 incoming ones. Which do you reach for?

Word frequencies

Write the code

Count how often each word appears and print the results sorted by word.

Solution unlocks after 3 attempts

Chain the transforms

Write the code

From the orders below, print the total value of orders over 20 that were placed by members, as one number.

Solution unlocks after 3 attempts

Deduplicate, keeping order

Write the code

Remove duplicates from an array while preserving first-seen order. A plain Set would lose the order.

Solution unlocks after 3 attempts
Explain itSaved on this device. Never graded.

Copy-on-write lets Swift give you value semantics without copying on every assignment. Explain in your own words what a program would have to do differently if collections were reference types instead — and name one bug that would become possible.

Checkpoint

You can now:

  • Choose between Array, Dictionary and Set from the question you need answered
  • Transform with map, filter, reduce, compactMap and sorted
  • Explain value semantics and copy-on-write, and predict what assignment does

Next up: functions — argument labels, defaults, and why Swift function names read like sentences.

How well do you know this now? Rating yourself honestly, then being tested on it, is how you find out where your intuition is wrong.