Swift
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Optionals
-
Section 4: Object-Oriented and Value Types
-
Section 5: Memory Management
-
Section 6: Generics and Error Handling
-
Section 7: Concurrency
-
Section 8: Working with Collections
-
Section 9: Codable and Data Handling
-
Section 10: Protocol-Oriented Programming
-
Section 11: Testing and Tooling
-
Section 12: Practical Projects
-
Section 13: Interview Practice
-
Section 14: More Practice Exercises
-
Section 15: More Standard Library
-
Section 16: Advanced Concurrency
-
Section 17: Foundation Framework Deep Dive
-
Section 18: URLSession and Networking Deep Dive
-
Section 19: Combine Framework
-
Section 20: SwiftUI Fundamentals for Swift Developers
-
Section 21: Server-Side Swift with Vapor
-
Section 22: Swift Package Manager Deep Dive
-
Section 23: Swift Concurrency Deep Dive
-
Section 24: More Language Features
-
Section 25: Error Handling Deep Dive
-
Section 26: Testing Deep Dive
-
Section 27: Data Structures and Algorithms in Swift
-
Section 28: More Practice Exercises
-
Section 29: More Interview Practice
-
Section 30: Swift Macros (Swift 5.9+)
-
Section 31: Property Wrappers Ecosystem
-
Section 32: Swift Interop Deep Dive
-
Section 33: iOS App Architecture Patterns
-
Section 34: Performance and Debugging
-
Section 35: App Distribution and CI/CD
-
Section 36: More Practical Projects
-
Section 37: SwiftData and Persistence
-
Section 38: More Design Patterns
-
Section 39: More Review and Practice
-
Section 40: More Foundation Deep Dive
-
Section 41: Advanced Collections in Swift
-
Section 42: Advanced Generics Practice
-
Section 43: UIKit for Legacy and Hybrid Apps
-
Section 44: watchOS and visionOS Development Basics
-
Section 45: More Networking Patterns
-
Section 46: More Testing Practice
-
Section 47: Accessibility in Swift Apps
-
Section 48: Localization
-
Section 49: More Practical Projects Round 2
-
Section 50: Swift Charts Framework
-
Section 51: More Interview and Algorithm Practice
-
Section 52: Final Practice and Mastery
-
Section 53: Swift Compiler and Build System
-
Section 54: More Concurrency Practice
-
Section 55: App Store Guidelines and Review
-
Section 56: More Design and Architecture
221: Solving Array and String Problems in Swift
Imagine you're standing in front of a long conveyor belt at a distribution center. Boxes are sliding past you one by one. If I asked you to find two boxes that together weigh exactly 50 lbs, you wouldn't just pick up the first box and then walk back to the start of the belt for every single other box to check the weight. That would be exhausting and incredibly slow. Instead, you'd probably use your hands as markers—maybe one hand at the start and one at the end—and slide them toward each other based on the total weight you're seeing. Or, if I asked you to find the longest stretch of boxes that are all the same color, you'd keep a "window" of your vision focused on a section of the belt, expanding it as long as the color matches and shrinking it the moment it doesn't.
In Swift, solving array and string problems is exactly like managing that conveyor belt. The "belt" is your Array or String, and your "hands" are your indices. The trick isn't just knowing the syntax; it's knowing which movement pattern to use so you aren't looping through the same data a thousand times.
Moving the Markers: The Two-Pointer Technique
I see a lot of developers default to nested for loops whenever they need to compare elements. But nested loops are often a sign that you're doing more work than necessary. The two-pointer approach is your best friend when you're dealing with sorted data or looking for pairs.
Take a palindrome check, for example. You don't need to reverse the whole string and compare it to the original—that allocates extra memory. Instead, put one pointer at the start and one at the end, and move them toward the center.
func isPalindrome(_ text: String) -> Bool {
let characters = Array(text.lowercased())
var left = 0
var right = characters.count - 1
while left < right {
if characters[left] != characters[right] {
return false
}
left += 1
right -= 1
}
return true
}
Notice how I converted the string to an Array first? I'll get to why in a second, but for now, focus on the logic: we only traverse the list once. That's a massive win for performance.
Expanding and Contracting the Sliding Window
Sometimes you aren't looking for a pair, but a segment. This is where the "Sliding Window" comes in. Think of it like a magnifying glass that you slide across the array. You expand the right side to include more data, and when a certain condition is broken, you shrink the left side until the condition is valid again.
Let's say you need to find the length of the longest substring without repeating characters. You can't just use a simple loop because the "valid" part of your string changes size dynamically.
func longestUniqueSubstring(_ s: String) -> Int {
let chars = Array(s)
var maxLength = 0
var start = 0
var seenChars = Set<Character>()
for end in 0..
The while loop inside the for loop might look like it's making the code slow, but it's not. Each element is added to the set once and removed once. In the industry, we call this O(n) time complexity. It's the difference between a professional implementation and a prototype that crashes on large datasets.
The Swift String Index Trap
Here is a bit of a warning: Swift strings are not like arrays. You can't just call myString[5]. Because Swift supports Unicode (meaning one "character" might actually be multiple bytes under the hood), calculating the position of a character takes time. This is why, in the examples above, I cast the strings to [Character] using Array(text).
If you're working with massive strings where converting to an array would eat too much memory, you'll have to use String.Index. It's clunkier—you have to use index(after:) and index(before:)—but it's the "correct" way to handle strings without copying them into a new array. I usually start with the array conversion for clarity during interviews or quick tasks, but keep the index methods in your back pocket for production-level memory optimization.
Tracking Frequency with Dictionaries
Finally, when the problem asks about "the first unique character" or "how many times X appears," stop thinking about loops and start thinking about Dictionaries. A dictionary acts as your "notepad" on the conveyor belt, allowing you to remember everything you've seen in a single pass.
If you need to find the first character that doesn't repeat, you do two passes: one to count everything, and one to find the first character whose count is exactly one. It's clean, it's fast, and it avoids the "nested loop" nightmare.
📋 Practical Task
Build a "Most Frequent Word" Analyzer
You're building a feature for a text analysis tool. Your task is to write a function that takes a string of text and returns the word that appears most frequently. If there is a tie, any of the most frequent words will do.
Requirements:
- The function should be case-insensitive (e.g., "Swift" and "swift" are the same word).
- Ignore punctuation marks like commas and periods.
- Return the word as a string, or
nilif the input string is empty.
Starter Code:
func findMostFrequentWord(in text: String) -> String? {
// Your implementation here
}
// Test it out:
let sampleText = "Swift is great. Learning Swift is fun, and Swift is powerful!"
print(findMostFrequentWord(in: sampleText) ?? "No word found")
// Expected output: "swift"There are no comments for now.