12 essential data structures for Swift developers. Each one explained with Big O complexity, animated visuals, and real code samples you can copy.
[T] (fixed)Fixed-size, contiguous block of memory. Elements are stored sequentially and accessed by index in constant time. The foundation of most other data structures.
arr[0] = 7
Complexity
When to use
// Fixed-size array via tuple or unsafe buffer
let fixed: [Int] = [10, 20, 30, 40, 50]
var copy = fixed
copy[0] = 99 // O(1) - direct index access
let val = copy[2] // O(1) - read by index
// Iterate
for item in copy {
print(item, terminator: " ")
}
// Search and sort
let sorted = copy.sorted() // O(n log n)
let idx = copy.firstIndex(of: 30) // O(n)[T] (Array)Resizable array that automatically grows when capacity is exceeded. The most commonly used data structure in most languages. Doubles its internal storage when full, giving amortized O(1) appends.
arr[0] = 7
Complexity
When to use
var names = ["Alice", "Bob", "Charlie"]
names.append("Diana") // O(1) amortized
names.insert("Eve", at: 1) // O(n) - shifts elements
names.removeLast() // O(1)
names.remove(at: 0) // O(n) - shifts elements
let has = names.contains("Bob") // O(n)
let idx = names.firstIndex(of: "Bob") // O(n)
names.sort() // O(n log n) - in-place
let squares = (0..<10).map { $0 * $0 } // O(n) - functional transformArray (as stack)Last-In-First-Out (LIFO) collection. Only the top element is accessible. Used for tracking state that must be unwound in reverse order.
Push 10
Complexity
When to use
// Swift has no built-in Stack - use Array
var stack: [Int] = []
stack.append(10) // O(1) - push
stack.append(20)
stack.append(30)
let top = stack.last! // O(1) - peek -> 30
let val = stack.removeLast() // O(1) - pop -> 30
// Interview pattern: valid parentheses
func isValid(_ s: String) -> Bool {
var st: [Character] = []
let pairs: [Character: Character] = ["(": ")", "[": "]", "{": "}"]
for c in s {
if let close = pairs[c] { st.append(close) }
else if st.isEmpty || st.removeLast() != c { return false }
}
return st.isEmpty
}Array / DequeFirst-In-First-Out (FIFO) collection. Elements are added at the back and removed from the front. Fundamental for breadth-first processing.
Enqueue 10
Complexity
When to use
// Simple queue using Array (or use Deque from Swift Collections)
var queue: [String] = []
queue.append("Task A") // O(1) - enqueue
queue.append("Task B")
queue.append("Task C")
let first = queue.first! // O(1) - peek
let val = queue.removeFirst() // O(n) - dequeue (use Deque for O(1))
// Interview pattern: BFS
func bfs(_ root: TreeNode?) {
guard let root else { return }
var q = [root]
while !q.isEmpty {
let node = q.removeFirst()
print(node.val, terminator: " ")
if let l = node.left { q.append(l) }
if let r = node.right { q.append(r) }
}
}Dictionary<K,V>Maps keys to values using a hash function for near-constant-time lookups. The single most important data structure for coding interviews. Every language has a built-in implementation.
hash("age") = 0
Complexity
When to use
var prices: [String: Int] = ["apple": 3, "banana": 5]
prices["cherry"] = 2 // O(1) - add
prices["apple"] = 10 // O(1) - update
let has = prices.keys.contains("banana") // O(1)
prices.removeValue(forKey: "cherry") // O(1)
let count = prices["mango", default: 0] // O(1) - safe lookup
// Interview pattern: Two Sum
func twoSum(_ nums: [Int], _ target: Int) -> [Int] {
var seen: [Int: Int] = [:]
for (i, num) in nums.enumerated() {
if let j = seen[target - num] { return [j, i] } // O(1)
seen[num] = i // O(1)
}
return []
}Set<T>Unordered collection of unique elements. Uses hashing internally for O(1) membership testing. Supports mathematical set operations like union, intersection, and difference.
hash("age") = 0
Complexity
When to use
var s: Set<Int> = [1, 2, 3, 4, 5]
s.insert(6) // O(1)
s.remove(1) // O(1)
let has = s.contains(4) // O(1) -> true
// Set operations
let other: Set<Int> = [4, 5, 6, 7]
let inter = s.intersection(other) // {4, 5, 6}
let union = s.union(other) // {2, 3, 4, 5, 6, 7}
let diff = s.subtracting(other) // {2, 3}
// Interview pattern: contains duplicate
func containsDuplicate(_ nums: [Int]) -> Bool {
return Set(nums).count != nums.count
}manual implSequence of nodes where each node points to the next (singly linked) or both next and previous (doubly linked). Efficient insertion and deletion at any known position, but no index-based access.
traversing: 5
Complexity
When to use
// Swift has no built-in linked list - manual implementation
class ListNode<T> {
var val: T
var next: ListNode?
init(_ val: T, _ next: ListNode? = nil) {
self.val = val; self.next = next
}
}
let head = ListNode(1, ListNode(2, ListNode(3)))
// Interview pattern: reverse a linked list
func reverse(_ head: ListNode<Int>?) -> ListNode<Int>? {
var prev: ListNode<Int>? = nil
var curr = head
while let node = curr {
let next = node.next // save
node.next = prev // reverse link
prev = node; curr = next
}
return prev // O(n) time, O(1) space
}sorted Array wrapperCollection of unique elements maintained in sorted order, typically backed by a balanced binary search tree (red-black tree). Supports range queries and O(log n) min/max.
search(8)
Complexity
When to use
// No built-in SortedSet - use a sorted Array wrapper
struct SortedSet<T: Comparable> {
private var items: [T] = []
mutating func insert(_ val: T) { // O(log n) search + O(n) shift
let idx = insertionIndex(for: val)
if idx < items.count, items[idx] == val { return }
items.insert(val, at: idx)
}
func contains(_ val: T) -> Bool { // O(log n) - binary search
let idx = insertionIndex(for: val)
return idx < items.count && items[idx] == val
}
private func insertionIndex(for val: T) -> Int {
items.firstIndex { $0 >= val } ?? items.count
}
var min: T? { items.first } // O(1)
var max: T? { items.last } // O(1)
}sorted keys patternKey-value pairs maintained in sorted key order, typically backed by a balanced BST. Enables ordered iteration and range lookups that hash maps cannot provide.
search(8)
Complexity
When to use
// No built-in SortedDictionary - use sorted keys pattern
var sd: [String: Int] = ["banana": 2, "apple": 5, "cherry": 1]
sd["date"] = 3 // O(1) insert
let val = sd["apple"] // O(1) -> 5
sd.removeValue(forKey: "cherry") // O(1)
// Iterate in sorted key order - O(n log n) for sort
for key in sd.keys.sorted() {
print("\(key): \(sd[key]!)")
}
// apple: 5, banana: 2, date: 3
let firstKey = sd.keys.sorted().first // "apple"
let lastKey = sd.keys.sorted().last // "date"Heap (Swift Collections)Collection where elements are dequeued by priority rather than insertion order. Typically implemented as a binary heap. Essential for shortest-path algorithms and top-K problems.
min-heap
Complexity
When to use
// Swift Collections provides Heap; or manual implementation
struct MinHeap<T: Comparable> {
var items: [T] = []
var peek: T? { items.first } // O(1)
mutating func insert(_ val: T) { // O(log n)
items.append(val); siftUp(items.count - 1)
}
mutating func extractMin() -> T? { // O(log n)
guard !items.isEmpty else { return nil }
items.swapAt(0, items.count - 1)
let min = items.removeLast(); siftDown(0)
return min
}
private mutating func siftUp(_ i: Int) {
var i = i
while i > 0, items[i] < items[(i - 1) / 2] {
items.swapAt(i, (i - 1) / 2); i = (i - 1) / 2
}
}
private mutating func siftDown(_ i: Int) { /* heapify down */ }
}actor-based cacheThread-safe hash map designed for concurrent read/write access from multiple threads. Uses fine-grained locking or lock-free techniques instead of a single global lock.
hash("age") = 0
Complexity
When to use
// Actor-based thread-safe dictionary (Swift 5.9+)
actor ConcurrentCache<K: Hashable, V> {
private var store: [K: V] = [:]
func get(_ key: K) -> V? {
store[key] // O(1) - isolated access
}
func set(_ key: K, _ value: V) {
store[key] = value // O(1) - isolated access
}
func getOrSet(_ key: K, factory: () -> V) -> V {
if let existing = store[key] { return existing }
let val = factory()
store[key] = val
return val
}
}
let cache = ConcurrentCache<String, Int>()
await cache.set("counter", 0)
let val = await cache.get("counter") // O(1)UnsafeBufferPointer / ArraySliceZero-copy view over a contiguous region of memory. Lets you reference a portion of an array or buffer without allocating new memory. Critical for performance-sensitive parsing and processing.
Span[0..3] = [1, 2, 3]
Complexity
When to use
// ArraySlice - zero-copy view over an Array
let data = [10, 20, 30, 40, 50]
let slice = data[1..<4] // O(1) - ArraySlice -> [20, 30, 40]
// slice shares storage with data (copy-on-write)
// UnsafeBufferPointer - raw memory view
data.withUnsafeBufferPointer { buffer in
let first = buffer[0] // O(1) - direct memory access
let count = buffer.count // 5
for val in buffer { print(val, terminator: " ") }
}
// UnsafeMutableBufferPointer for mutation
var mutable = [1, 2, 3]
mutable.withUnsafeMutableBufferPointer { buf in
buf[0] = 99 // O(1) - direct write
}Average-case time complexity. * = amortized.
| Structure | Access | Search | Insert | Delete |
|---|---|---|---|---|
| Array | O(1) | O(n) | O(n) | O(n) |
| Dynamic Array | O(1) | O(n) | O(1)* | O(n) |
| Stack | O(n) | O(n) | O(1)* | O(1) |
| Queue | O(n) | O(n) | O(1)* | O(1) |
| Hash Map | O(1) | O(1) | O(1)* | O(1) |
| Hash Set | N/A | O(1) | O(1)* | O(1) |
| Linked List | O(n) | O(n) | O(1) | O(1) |
| Sorted Set | O(n) | O(log n) | O(log n) | O(log n) |
| Sorted Map | O(log n) | O(log n) | O(log n) | O(log n) |
| Priority Queue | O(n) | O(n) | O(log n) | O(log n) |
| Concurrent Map | O(1) | O(1) | O(1)* | O(1) |
| Memory View | O(1) | O(n) | N/A | N/A |
| I need to... | Use |
|---|---|
| Store items by index, resize dynamically | List / Dynamic Array |
| Map keys to values with O(1) lookup | HashMap / Dictionary |
| Track unique items, check existence in O(1) | HashSet / Set |
| Last-in-first-out (undo, DFS, brackets) | Stack |
| First-in-first-out (BFS, task queues) | Queue |
| Keep elements sorted at all times | SortedSet / TreeSet |
| Process items by priority (Dijkstra, top-K) | PriorityQueue / Heap |
| Insert/delete at a known position in O(1) | LinkedList |
| Sorted key-value pairs | SortedDictionary / TreeMap |
| Thread-safe shared cache | ConcurrentDictionary |
| Slice arrays/strings without copying | Span / Slice / memoryview |
The most commonly used are dynamic arrays (List/ArrayList/vector), hash maps (Dictionary/HashMap/dict), and hash sets. For interviews, also know stacks, queues, trees, and priority queues. These cover 90%+ of coding interview problems.
Start with the dynamic array and hash map. Together they solve the majority of interview problems. Then learn stacks (for DFS, bracket matching) and queues (for BFS). After that, tackle trees, heaps, and graphs.
No. Big O measures algorithmic complexity, not language-specific performance. A hash map lookup is O(1) whether you use Python dict, Java HashMap, or C# Dictionary. Constant factors differ (C++ is faster than Python in wall-clock time), but Big O is the same.
It depends on the language runtime. Check the Swift standard library documentation for heap or priority queue support.
Practice with an AI interviewer that asks you to implement, optimize, and explain data structure choices in real time.
Try a free mock interviewarrow_forward