Arrays in Swift: The Core Building Block of DSA
Master the fundamental data structure that powers 80% of iOS interview questions — from basic operations to advanced techniques that…
Data Structures & Algorithms in Swift

So I was helping a friend prep for his interview last month, and he kept getting stuck on these array problems. Not complicated ones either — basic stuff like finding duplicates or reversing subarrays.
What got me though? This person ships production code to millions of users. He knows Swift inside out. But put him in front of a whiteboard with an array problem and suddenly it’s like his brain freezes.
I’ve been there too, if I’m being honest.
The problem isn’t that we don’t know what arrays are. We use them every single day. append() this, .filter() that, maybe throw in a .map() when we're trying to look clever in code review. But there's this massive gap between using arrays and actually understanding how they work at a fundamental level.
And look — I get it. When you’re building a SwiftUI view or debugging some weird UIKit lifecycle issue, who cares about contiguous memory allocation, right?
Except… interviews care. A lot. And honestly, once you understand this stuff, it actually makes you better at the day-to-day coding too. You start seeing patterns everywhere.
So that’s what we’re doing here. No fluff, no “here’s how to create an empty array” nonsense (though okay, we’ll cover some of that too). This is the stuff that actually matters.
📺 By the way — I’m working on a video series covering these exact patterns on Swift Pal. Should be up in a couple weeks. Check it out: _https://youtube.com/@swift-pal_
🤔 What Actually Are Arrays? (And Why Should You Care)
Alright, so… arrays. Everyone knows what they are, right? Ordered collection of stuff. Elements go in, you can pull them out by index. Basic.
But here’s the thing nobody really explains properly — and I didn’t get this until way later than I should have — arrays are contiguous blocks of memory.
That’s it. That one fact? It’s responsible for literally everything about how arrays work. Every single performance quirk, every trade-off, all of it traces back to this.
I like to think of it like… okay, imagine a row of parking spots. All the same size, all next to each other. When you make an array, Swift grabs a bunch of these spots in a row and says “these are yours.” Element zero goes in the first spot, element one in the next, and so on.
// You write this...
var numbers = [10, 20, 30, 40, 50]
// But in memory it actually looks like:
// [10][20][30][40][50] <- all lined up, no gaps
// ↑ ↑ ↑ ↑ ↑
// 0 1 2 3 4 <- these are just positionsSeems almost too simple? Yeah, I thought so too. But this design decision — this ONE decision — is why arrays are crazy fast for some things and surprisingly terrible for others.
💾 Memory Layout (The Stuff That Actually Matters in Interviews)
Okay so this is where it clicks. Or at least, where it clicked for me.
Because everything’s laid out in a nice straight line in memory, Swift can do this really clever trick to find any element instantly. No searching required.
Say you want numbers[3]. Swift doesn't loop through indices 0, 1, 2 to get there. Nah. It just does some basic math:
Address of numbers[3] = Base Address + (3 × Size of Each Element)Bam. Done. O(1) time complexity.
Interviewers LOVE asking about this, by the way. If you can explain this in an interview, you’re already ahead of like 60% of candidates.
But — and yeah, there’s always a catch — this same feature that makes lookups so fast? It makes insertions at the start super expensive. Think about it. If you want to insert something at index 0, Swift has to physically move every other element one spot to the right. The whole contiguous thing means there’s no shortcuts here.
var items = [1, 2, 3, 4, 5]
// Inserting at the start
items.insert(0, at: 0) // [0, 1, 2, 3, 4, 5]
// What Swift actually has to do:
// - Make room at position 0
// - Move the 1 to position 1
// - Move the 2 to position 2
// - Move the 3 to position 3
// - Keep going for every element...So yeah, O(n) complexity. And if you’re in an interview and you insert at the beginning without mentioning this performance hit? That’s gonna raise some eyebrows.
⚡ Time Complexity: The Numbers That’ll Save Your Interview
So Big-O notation, right? I used to hate this stuff. It felt so theoretical and detached from actual coding.
But then I realized — it’s not about memorizing that “insertion is O(n).” It’s about understanding WHY it’s O(n). Once you get the why, the numbers just make sense.
Accessing by index? O(1)
let value = numbers[3] // Instant. Always.Doesn’t matter if your array has 10 elements or 10 million. That math formula we talked about earlier means direct memory access every time.
Searching for a value? O(n)
if numbers.contains(42) {
// Gotta check 'em all
}Swift literally has to walk through the array element by element. Well, unless it’s sorted… but that’s a different conversation.
Adding to the end? O(1) amortized
numbers.append(60) // Usually instantOkay this one trips people up. “Amortized” means it’s usually O(1), but every once in a while Swift runs out of space and has to allocate a bigger chunk of memory and copy everything over. But averaged across many append operations, it works out to constant time.
Interviewers eat this stuff up. If you can explain amortized complexity without stumbling, you’re golden.
Inserting at the start or middle? O(n)
numbers.insert(5, at: 0) // Oof, expensiveEverything after that insertion point has to shift over. The closer to the beginning you insert, the worse it gets performance-wise.
Deleting stuff? Also O(n)
numbers.remove(at: 2) // Same shifting problemGotta close the gap somehow, which means shifting elements to fill it.
Here’s something nobody told me when I was learning this — in interviews, you should basically always mention time complexity when you’re picking a data structure. Shows you’re thinking about performance, not just “making it work.” That distinction matters way more than people realize.
🔧 Essential Array Operations (The Ones You’ll Use Every Day)
Let’s get practical for a bit. Theory’s great and all, but what operations do you actually need to know?
Creating Arrays
// Empty array (couple different ways)
var empty: [Int] = []
var alsoEmpty = [String]()
// With some values already in there
var numbers = [1, 2, 3, 4, 5]
// This one's underrated - repeating values
var zeros = Array(repeating: 0, count: 5) // [0, 0, 0, 0, 0]
// From a range (super handy)
var range = Array(1...5) // [1, 2, 3, 4, 5]That Array(repeating:count:) thing? I use it constantly for DSA problems. Dynamic programming, matrix stuff, anywhere you need to initialize with default values. Total game-changer once you know it exists.
Adding Elements
var fruits = ["apple", "banana"]
// Most common - add to end
fruits.append("orange") // ["apple", "banana", "orange"]
// Add multiple at once
fruits.append(contentsOf: ["grape", "mango"])
// Stick something in the middle
fruits.insert("kiwi", at: 1)
// Now: ["apple", "kiwi", "banana", "orange", "grape", "mango"]Removing Elements
var numbers = [1, 2, 3, 4, 5]
// Remove at specific index
numbers.remove(at: 2) // removes 3, and actually returns it too
// Remove first or last (these come up a lot)
let first = numbers.removeFirst() // removes and returns 1
let last = numbers.removeLast() // removes and returns 5
// Nuke everything
numbers.removeAll()
// Remove based on some condition
var values = [1, 2, 3, 4, 5, 6]
values.removeAll { $0 % 2 == 0 } // bye bye evens, left with [1, 3, 5]Quick tip that caught me off guard once: removeFirst() is O(n) but removeLast() is O(1). Makes sense when you think about it—removing from the front means shifting everything, but removing from the back? Nothing to shift. Small details like this are what interviewers listen for.
Getting Elements Out
let numbers = [10, 20, 30, 40, 50]
// Direct access (the obvious way)
let third = numbers[2] // 30
// Safe access - check if index exists
if numbers.indices.contains(2) {
print(numbers[2])
}
// First and last are properties (and they return optionals!)
let first = numbers.first // Optional(10)
let last = numbers.last // Optional(50)See how first and last give you optionals? That's because the array could be empty. This trips up so many people in interviews—they forget to unwrap and boom, compiler error mid-interview. Don't be that person.
Changing Elements
var items = [1, 2, 3, 4, 5]
// Just assign to an index
items[2] = 99 // [1, 2, 99, 4, 5]
// Swapping (you'll use this in every sorting algorithm, I swear)
items.swapAt(0, 4) // [5, 2, 99, 4, 1]That swapAt() method? Memorize it. Every single sorting algorithm question uses it.
Higher-Order Functions (The Fancy Stuff)
let numbers = [1, 2, 3, 4, 5]
// Map - transform everything
let doubled = numbers.map { $0 * 2 } // [2, 4, 6, 8, 10]
// Filter - keep only what matches
let evens = numbers.filter { $0 % 2 == 0 } // [2, 4]
// Reduce - smash it all together
let sum = numbers.reduce(0, +) // 15
// CompactMap - map but skip the nils
let strings = ["1", "2", "three", "4"]
let validNumbers = strings.compactMap { Int($0) } // [1, 2, 4]
// Sorted (doesn't change the original!)
let sorted = numbers.sorted(by: >) // [5, 4, 3, 2, 1]These are clean and honestly pretty elegant. But remember — each one creates a new array. If you’re working with huge datasets, that memory allocation adds up fast.
🚀 Advanced Techniques (Interview Patterns You MUST Know)
Okay so this is where we get into the stuff that actually shows up in interviews constantly. Once you know these patterns, you start seeing them everywhere.
Two Pointers
This one’s probably the most common pattern you’ll see. Basic idea: use two indices to move through the array. Sometimes they start at opposite ends, sometimes they move at different speeds.
// Example: Is this array a palindrome?
func isPalindrome(_ array: [Int]) -> Bool {
var left = 0
var right = array.count - 1
while left < right {
if array[left] != array[right] {
return false
}
left += 1
right -= 1
}
return true
}
// Try it
let palindrome = [1, 2, 3, 2, 1]
print(isPalindrome(palindrome)) // trueWhy’s this so good? You’re processing the whole array in O(n) time using basically no extra space. O(1) space complexity. Clean.
Sliding Window
This one blew my mind when I first learned it. Perfect for subarray problems. Instead of recalculating everything for each position, you just slide your window and update what changed.
// Find the max sum of any k consecutive elements
func maxSumSubarray(_ array: [Int], k: Int) -> Int? {
guard array.count >= k else { return nil }
// Get the sum of first window
var windowSum = 0
for i in 0..<k {
windowSum += array[i]
}
var maxSum = windowSum
// Now slide it
for i in k..<array.count {
// Remove left element, add right element
windowSum = windowSum - array[i - k] + array[i]
maxSum = max(maxSum, windowSum)
}
return maxSum
}
// Test
let numbers = [2, 1, 5, 1, 3, 2]
print(maxSumSubarray(numbers, k: 3) ?? 0) // 9 (that's [5, 1, 3])Instead of recalculating the sum every time (which would be O(n×k)), we just subtract the element leaving the window and add the new one. O(n) total. Way better.
Fast and Slow Pointers
Sometimes called “tortoise and hare.” One pointer moves twice as fast as the other. This pattern’s more common with linked lists, but it works with arrays too in certain problems.
// Finding a duplicate when numbers are in range 1 to n
func findDuplicate(_ nums: [Int]) -> Int {
var slow = nums[0]
var fast = nums[0]
// Find where they meet
repeat {
slow = nums[slow]
fast = nums[nums[fast]]
} while slow != fast
// Find the actual duplicate
slow = nums[0]
while slow != fast {
slow = nums[slow]
fast = nums[fast]
}
return slow
}
// Note: only works with specific constraintsNot gonna lie, this one felt like black magic the first time I saw it. If it doesn’t click right away, that’s normal. Took me a while too.
Prefix Sum
If you need to calculate subarray sums repeatedly, build a prefix sum array once upfront. Then every query is O(1) instead of O(n).
// Build the prefix sum
func buildPrefixSum(_ array: [Int]) -> [Int] {
var prefixSum = Array(repeating: 0, count: array.count + 1)
for i in 0..<array.count {
prefixSum[i + 1] = prefixSum[i] + array[i]
}
return prefixSum
}
// Get sum from index i to j
func rangeSum(_ prefixSum: [Int], _ i: Int, _ j: Int) -> Int {
return prefixSum[j + 1] - prefixSum[i]
}
// Use it
let nums = [1, 2, 3, 4, 5]
let prefix = buildPrefixSum(nums)
print(rangeSum(prefix, 1, 3)) // [2, 3, 4] = 9
print(rangeSum(prefix, 0, 4)) // whole thing = 15I’ve used this pattern so many times in interviews. Range sum queries become instant. Without it? You’re stuck recalculating every time.
In-Place Reversal
Reversing without extra space:
func reverseArray(_ array: inout [Int], _ start: Int, _ end: Int) {
var left = start
var right = end
while left < right {
array.swapAt(left, right)
left += 1
right -= 1
}
}
// Reverse everything
var numbers = [1, 2, 3, 4, 5]
reverseArray(&numbers, 0, numbers.count - 1)
print(numbers) // [5, 4, 3, 2, 1]
// Or just part of it
var items = [1, 2, 3, 4, 5]
reverseArray(&items, 1, 3)
print(items) // [1, 4, 3, 2, 5]That inout keyword is key—we're modifying the original array, not making a copy. Saves memory, which interviewers appreciate.
💡 Interview Patterns (The Questions That Keep Coming Up)
After going through hundreds of interview questions (both asking and answering them), I’ve noticed certain patterns that repeat endlessly. Here are the ones you absolutely need to know:
Pattern 1: Find Missing/Duplicate Elements
// Find missing number in array containing 1 to n
func findMissingNumber(_ nums: [Int]) -> Int {
let n = nums.count + 1
let expectedSum = n * (n + 1) / 2
let actualSum = nums.reduce(0, +)
return expectedSum - actualSum
}
let numbers = [1, 2, 4, 5, 6] // Missing 3
print(findMissingNumber(numbers)) // 3The math approach is elegant and O(n) time with O(1) space. Interviewers love this.
Pattern 2: Merge/Combine Sorted Arrays
func mergeSortedArrays(_ arr1: [Int], _ arr2: [Int]) -> [Int] {
var result = [Int]()
var i = 0, j = 0
// Compare and merge
while i < arr1.count && j < arr2.count {
if arr1[i] <= arr2[j] {
result.append(arr1[i])
i += 1
} else {
result.append(arr2[j])
j += 1
}
}
// Append remaining elements
while i < arr1.count {
result.append(arr1[i])
i += 1
}
while j < arr2.count {
result.append(arr2[j])
j += 1
}
return result
}
let merged = mergeSortedArrays([1, 3, 5], [2, 4, 6])
print(merged) // [1, 2, 3, 4, 5, 6]This is literally the merge step from merge sort. Know it cold.
Pattern 3: Rotate Array
func rotateArray(_ nums: inout [Int], _ k: Int) {
let k = k % nums.count // Handle k > array length
// Reverse entire array
nums.reverse()
// Reverse first k elements
var firstPart = Array(nums[0..<k])
firstPart.reverse()
// Reverse remaining elements
var secondPart = Array(nums[k..<nums.count])
secondPart.reverse()
// Combine
nums = firstPart + secondPart
}
// More elegant approach using array slicing and reversal
func rotateElegant(_ nums: inout [Int], _ k: Int) {
let k = k % nums.count
nums = Array(nums[nums.count - k..<nums.count] + nums[0..<nums.count - k])
}
var numbers = [1, 2, 3, 4, 5, 6, 7]
rotateElegant(&numbers, 3)
print(numbers) // [5, 6, 7, 1, 2, 3, 4]Rotation questions are everywhere. Practice both approaches.
Pattern 4: Product of Array Except Self
// Without division (the tricky constraint!)
func productExceptSelf(_ nums: [Int]) -> [Int] {
var result = Array(repeating: 1, count: nums.count)
// Left products
var leftProduct = 1
for i in 0..<nums.count {
result[i] = leftProduct
leftProduct *= nums[i]
}
// Right products
var rightProduct = 1
for i in stride(from: nums.count - 1, through: 0, by: -1) {
result[i] *= rightProduct
rightProduct *= nums[i]
}
return result
}
let input = [1, 2, 3, 4]
print(productExceptSelf(input)) // [24, 12, 8, 6]
// Explanation: [2*3*4, 1*3*4, 1*2*4, 1*2*3]This one’s brilliant because it looks impossible at first — “how do I get products without division?” — but the two-pass approach is genius.
Pattern 5: Kadane’s Algorithm (Maximum Subarray Sum)
func maxSubarraySum(_ nums: [Int]) -> Int {
var maxSoFar = nums[0]
var maxEndingHere = nums[0]
for i in 1..<nums.count {
maxEndingHere = max(nums[i], maxEndingHere + nums[i])
maxSoFar = max(maxSoFar, maxEndingHere)
}
return maxSoFar
}
let values = [-2, 1, -3, 4, -1, 2, 1, -5, 4]
print(maxSubarraySum(values)) // 6 (subarray [4, -1, 2, 1])Kadane’s algorithm is one of those things that looks simple but is incredibly powerful. It’s O(n) and solves a problem that looks like it should be O(n²).
Pattern 6: Dutch National Flag (Three-Way Partitioning)
// Sort array of 0s, 1s, and 2s in one pass
func dutchFlagSort(_ nums: inout [Int]) {
var low = 0
var mid = 0
var high = nums.count - 1
while mid <= high {
if nums[mid] == 0 {
nums.swapAt(low, mid)
low += 1
mid += 1
} else if nums[mid] == 1 {
mid += 1
} else { // nums[mid] == 2
nums.swapAt(mid, high)
high -= 1
}
}
}
var colors = [2, 0, 2, 1, 1, 0]
dutchFlagSort(&colors)
print(colors) // [0, 0, 1, 1, 2, 2]Named after the Dutch flag (red, white, blue), this pattern extends to any three-way partitioning problem. O(n) time, O(1) space.
🎬 Wrapping This Up
Look, arrays might seem basic. Hell, they are basic. But that’s exactly why they’re so important.
Every complex data structure you’ll encounter builds on the concepts we covered here. Understanding arrays deeply — not just how to use them, but how they work, why they’re designed the way they are, and when to use them — that’s what separates developers who struggle with DSA from those who find it intuitive.
The patterns we covered? They’re not just academic exercises. I use variations of two pointers, sliding window, and prefix sums regularly in production iOS code. These aren’t “interview tricks” — they’re legitimate problem-solving techniques.
Here’s my advice: Don’t just read this once and move on. Pick 2–3 of these patterns and solve 5–10 problems using each. Actually code them out. Debug them. Break them. Fix them. That’s how the knowledge sticks.
And remember — when you’re stuck on an array problem, slow down. Draw it out. Walk through a small example by hand. Most array problems aren’t about clever tricks; they’re about clear thinking and understanding the fundamental operations.
📺 Want to see these patterns in action? I’m putting together a complete video series on Array techniques and interview patterns. Subscribe to Swift Pal at https://youtube.com/@swift-pal to catch it when it drops!
Now go forth and manipulate some arrays. You got this. 🚀
🎉 Enjoyed this article? Your support means the world to me!
💬 Drop a comment below! I love hearing about your experiences and answering questions
🎬 Subscribe on Youtube and become early subscribers of my channel: https://www.youtube.com/@swift-pal
💼 Let’s connect on LinkedIn for more professional insights: https://www.linkedin.com/in/karan-pal
Happy coding! 🚀
New articles, straight to your inbox.
No spam, no filler — just new writing on iOS, the web, and AI when it ships. Unsubscribe anytime.
Keep reading
Why I'm Rebuilding My Blog From Scratch (and Leaving Medium)
After years of publishing on someone else's platform, I'm moving my writing to a home I actually own. Here's the reasoning, and what I'm building instead.
ReadData Is the Model: The Most Ignored Part of AI
A beginner-friendly guide to why data quality beats model hype.
Read