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
121: Implementing a Linked List in Swift
A few years ago, I was reviewing a pull request for a junior dev who had built a music playback queue. He was using a standard Swift Array to store the songs. Every time a song finished, he called songs.remove(at: 0) to move to the next track. It worked fine during testing with five songs, but when we tested it with a user's 500-song playlist, the app hitched. That's because removing the first element of an array forces Swift to shift every single remaining element one position to the left. It's an O(n) operation that kills performance as the list grows.
This is the classic moment where you stop reaching for an array and start thinking about a Linked List. Unlike arrays, which occupy a contiguous block of memory, a linked list is a series of independent nodes scattered in memory, each holding a reference to the next one. To remove the first item, you don't shift anything; you just tell the list that the second item is now the first. It's an O(1) operation, and it's incredibly efficient for queues.
Defining the Atomic Node
To get this working in Swift, we first need a Node. Because a node needs to reference another node of the same type, we have to use a class. Structs won't work here because they are value types and cannot be recursive without indirect pointers.
class Node<T> {
var value: T
var next: Node?
init(value: T, next: Node? = nil) {
self.value = value
self.next = next
}
}
I used a generic T here because a linked list shouldn't care if it's storing Strings, Ints, or Song objects. The next property is optional because the very last node in your list—the tail—will always point to nil.
Wrapping it in a Management Class
You don't want your API users manually messing with Node objects; that's an implementation detail. Instead, we wrap everything in a LinkedList class that manages the head (the start) and the tail (the end). Keeping a reference to the tail is a pro move—it allows us to append items to the end of the list instantly without having to loop through the entire chain first.
class LinkedList<T> {
private var head: Node<T>>?
private var tail: Node<T>>?
var isEmpty: Bool {
return head == nil
}
func append(_ value: T) {
let newNode = Node(value: value)
if let tailNode = tail {
tailNode.next = newNode
} else {
head = newNode
}
tail = newNode
}
func popFirst() -> T? {
guard let headNode = head else { return nil }
head = headNode.next
if head == nil {
tail = nil
}
return headNode.value
}
}
Notice how popFirst handles the edge case where the list becomes empty. If the head becomes nil, we have to clear the tail as well, otherwise, we'd be holding onto a reference to a node that is no longer part of the list.
Walking the Chain
The one downside of a linked list is that you can't jump to a specific index like you can with array[42]. You have to start at the head and "walk" through the nodes. I usually implement this by making the list conform to Sequence, but for a basic implementation, a simple while loop does the trick.
func printAll() {
var current = head
while let node = current {
print(node.value)
current = node.next
}
}
It's a trade-off. You lose random access, but you gain the ability to grow and shrink your list at the boundaries without the memory overhead of shifting elements. In the music queue example, this change takes the playback transition from a noticeable stutter to a near-instant operation.
📋 Practical Task
Implementing a Music Playlist Queue
Your task is to complete the PlaylistQueue implementation. You need to build a linked list that manages Song objects. Each Song has a title and an artist.
Implement the following in a new Swift file:
- A
Songstruct withtitleandartistproperties. - A
Nodeclass to hold theSongand the reference to thenextnode. - A
PlaylistQueueclass that provides:- An
enqueue(song:)method to add a song to the end of the playlist. - A
dequeue()method that removes and returns the song at the front of the playlist. - A
peek()method that returns the song at the front without removing it.
- An
Test your implementation by adding three different songs and calling dequeue() twice to ensure the songs are returned in the exact order they were added (First-In, First-Out).
There are no comments for now.