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 }| Need | Container | Complexity |
|---|---|---|
| Ordered list | Array | Append is amortised O(1), insert at front O(n) |
| Lookup by key | Dictionary | O(1) average |
| Uniqueness | Set | O(1) average for contains |
| Ordered keys | Sort the keys | O(n log n) |
| Frequency count | Dictionary with default | One 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. Substringis a view, not a copy. Converting toStringwhen it outlives the original avoids retaining a huge buffer for a small piece.countis grapheme clusters, which is what a user perceives as a character. Useutf8.countwhen sizing a network payload.- Comparing with
localizedStandardComparegives 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.Related
Networking, persistence and Codable Generics, opaque types and protocol design
Last refreshed 2026-09-18.