Collections, strings and the standard library

Arrays, dictionaries and sets, map and filter and reduce, String versus Substring, Unicode and grapheme clusters, Date and Calendar, and Codable basics.

Choosing the right container

var scores: [String: Int] = ["ada": 91, "grace": 87]
let visited: Set<String> = ["home", "search", "home"]     // two elements

scores["linus"] = 78
scores["ada", default: 0] += 5        // avoids a force unwrap
let grouped = Dictionary(grouping: scores.keys, by: { $0.first! })

let sorted = scores.sorted { $0.value > $1.value }
let top = scores.max { $0.value < $1.value }              // ("ada", 96)

// transform in one pass instead of building intermediate arrays
let summary = scores
    .filter { $0.value >= 80 }
    .map { $0.key.capitalized }
    .sorted()
    .joined(separator: ", ")

// reduce with an explicit result type
let total = scores.values.reduce(into: 0) { $0 += $1 }
NeedContainerComplexity
Ordered listArrayAppend is amortised O(1), insert at front O(n)
Lookup by keyDictionaryO(1) average
UniquenessSetO(1) average for contains
Ordered keysSort the keysO(n log n)
Frequency countDictionary with defaultOne pass

Strings, Substring and Unicode

let text = "Café naïve"       // combining marks

text.count                      // 11 grapheme clusters, not 13 scalars
text.utf8.count                 // bytes on the wire
text.unicodeScalars.count       // scalars

let index = text.index(text.startIndex, offsetBy: 4)
let prefix = text[..<index]     // Substring, shares storage with text

// a Substring keeps the whole original string alive; convert when it escapes
let piece: String = String(prefix)

let parts = "a,b,,c".split(separator: ",", omittingEmptySubsequences: false)
// ["a", "b", "", "c"]

let localized = String(localized: "greeting", defaultValue: "Hello, \(name)")

let cleaned = text
    .folding(options: [.diacriticInsensitive, .caseInsensitive], locale: .current)
  • Never index a String by an integer. Use index(_:offsetBy:) or an existing index; the cost is linear in the number of characters.
  • Substring is a view, not a copy. Converting to String when it outlives the original avoids retaining a huge buffer for a small piece.
  • count is grapheme clusters, which is what a user perceives as a character. Use utf8.count when sizing a network payload.
  • Comparing with localizedStandardCompare gives the ordering a person expects for names and filenames.

Dates, calendars and Codable

let now = Date.now
let calendar = Calendar(identifier: .gregorian)
let startOfDay = calendar.startOfDay(for: now)
let days = calendar.dateComponents([.day, .month, .year], from: now)

let formatter = Date.FormatStyle(date: .abbreviated, time: .shortened)
    .locale(Locale(identifier: "en_GB"))
let label = now.formatted(formatter)

// time zones change; a Date is an instant, not a wall-clock time
let deadline = calendar.date(byAdding: .day, value: 30, to: now)!
let daysLeft = calendar.dateComponents([.day], from: now, to: deadline).day ?? 0

struct Event: Codable, Equatable {
    let id: UUID
    let title: String
    let at: Date

    enum CodingKeys: String, CodingKey {
        case id, title
        case at = "occurred_at"
    }
}

let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601
let events = try decoder.decode([Event].self, from: data)
⚠️
Never add or subtract seconds to move by days. Daylight saving makes a day 23 or 25 hours long, so addingTimeInterval(86_400) drifts. Use Calendar, and store instants as UTC while formatting in the user's time zone.

FAQ

Should I use Array or ContiguousArray?
For a stored property of a generic or existential type, ContiguousArray removes a bridging cost and is measurably faster. The difference is negligible for concrete value types.
Why is string concatenation in a loop slow?
Each += may allocate. Accumulate into an array and join once, or reserve capacity on a ContiguousArray and append, which is O(n) instead of potentially quadratic.

Networking, persistence and Codable Generics, opaque types and protocol design

Last refreshed 2026-09-18.