Some of the most sophisticated, beautifully designed, high-quality tech products you can find are branded Apple, and with a high-quality product comes a high-quality coding language.
Swift was introduced in 2014 as a more powerful and intuitive coding language than its older parent, Objective-C.
Most recruiters look for native iOS developers and engineers with knowledge of Swift, one of the most modern, reliable, and robust coding languages.
But preparing for interviews can be stressful, especially when it comes to questions that you might be asked in the interview.
In this article, you'll learn the answers to common Swift interview questions, as well as how you can be better prepared for technical interviews.
If you’re at this stage, you've likely already taken a coding test for the company you’ve applied for, and you passed it.
But now it’s time for the next round where you need to self-promote and prove your knowledge.
In this article, you'll refresh your knowledge of some of the most common Swift interview questions and their answers.
This question can be tricky. You know how to code using Swift, but it might be your first coding language and you don’t necessarily know many others.
How are you supposed to make the comparison, then?
Swift is a modern, fast, and easy-to-read language. It was created specifically to develop native iOS apps.
These apps communicate directly with the hardware of the user’s device, which means that they will deliver faster and better performance than hybrid apps that rely on Flutter or React Native.
It's also a language that's built to be helpful.
It's type-safe, which means that it determines which values you can use in your code, which prevents accidentally passing different data types to an object, a helpful feature both for run-time and compile-time.
It uses automatic reference counting, which manages the memory for you, so you can focus on functionality.
If you're relatively new to coding for iOS, you may not have much knowledge of the original language, Objective-C.
Objective-C is essentially an extension of C, designed with additional object-oriented programming attributes.
However, Swift has a much more straightforward syntax.
Objective-C holds on to a number of legacy conventions that can make coding more complicated, such as requiring a semicolon at the end of a line, or brackets for Boolean expressions in if/else
statements.
Additionally, as a superset of C, Objective-C has limited ability to change or improve unless C makes those changes or improvements first.
Another major difference is that using Objective-C, your code is spread across two files, one for the header, which defines the classes and is stored in a file with the extension .h
, and the implementation file, stored in a file with the extension .m
.
These files have to be manually synchronized, which adds considerable overhead to your workflow.
With Swift, everything is combined in a single .swift
file, making it easier to have a comprehensive overview of what you're working on.
Generally speaking, coding requires an integrated development environment (IDE).
These applications are basically a workstation that enables developers to code, compile, and debug in a single interface. Each IDE has different features, depending on the coding languages that they host.
For programmers using Swift, Apple created Xcode, which runs exclusively on macOS.
Xcode provides simulators for testing, although you may still need a physical Apple device to test certain features, such as those involving camera use.
While you can code using Swift in any IDE, building, signing, and distributing native iOS apps is exclusive to Xcode, and your recruiters will expect you to know how to use it.
This makes it, along with a Mac computer, a necessity for native iOS Apps development.
One of the fundamental principles of programming in Swift is immutability.
You may be asked to talk about the importance of immutability in your Swift interview.
In order to prevent variables and data from changing within an application, immutability is used.
Swift data structures' immutability enables optimal data storage and consistent variable usage.
It also prevents mistakes related to accidentally changing or reusing constants and variables.
This may sound like a simple question, but even when you've used typealiases before, it can be a difficult concept to express in words, especially when you're put on the spot.
A typealias is a Swift function that allows you to assign another name—that is, an alias—to a data type.
It can be used for built-in types, such as String
, but also for complex types (such as closures) and user-defined types like classes or structs.
In code, it improves readability. For instance, we can replace the String
type with the name restaurant
by declaring a typealias for it:
typealias restaurant = String
Later, we can define an object to be of type String
by using the word restaurant
:
let name: restaurant = "KFC"
Swift will treat restaurant
as String
, but it makes it easier for other people reading your code to know exactly what you're referring to.
It's beneficial both for teamwork and for solo developers, as going back to old code sometimes proves less straightforward than you expect.
A collection of related values is an enumeration.
It is easy to write type-safe code using enumerations.
// these are our values
enum thumb: CaseIterable {
case up
case down
}
In this example, thumb has two unique cases, up
and down
.
You can call these in your code. Instead of calling a string called up
or down
, you can call thumb.up
for more clarity.
This helps make code more readable and less prone to errors.
While Swift is an easy-to-read coding language, it’s easy to get overwhelmed or confused, especially when you're working with hundreds or even thousands of lines of code.
It's important to keep your code clean and orderly, and there are a few ways to achieve that.
You need to indent your code correctly, and add code comments to each block to make it immediately obvious what each part of the code does.
You should also be sure to define your data names as something intuitive for your use case.
An example:
// these are our values
enum thumb: CaseIterable {
case up
case down
}
// this is our variable
var thumbs: thumb = thumb.up
// this is the function
func result() {
// let fate decide!
let random = thumb.allCases.randomElement()!
thumbs = random
// check your result…
switch thumbs {
case .up:
print("Thumbs up!")
case .down:
print("Sorry, bud. Thumbs down.")
}
}
result()
It's also a good idea to break the code apart instead of sticking all your assertions together. For instance, this example is difficult to read:
func validateUsername(_ string: String) -> Bool {
let bool = string.count > 7 && !string.contains(Character(" "))
return bool
}
The same assertions expressed another way is much easier:
func validateUsername(_ string: String) -> Bool {
var isLongEnough = string.count > 7
var hasNoSpaces = !string.contains(Character(" "))
let bool = isLongEnough && hasNoSpaces
return bool
}
Testing is one of the most important parts of your development cycle. As mentioned above, Xcode provides simulators of most Apple device types.
An interesting alternative to simulators is unit testing. By creating instances of data, you can run tests to make sure the result corresponds to what you expected, allowing you to quickly detect bugs and errors in your code without waiting for your app to build on a simulator.
However, it's important to note that most UI features can be tested on a simulator, features that involve the use of a device camera and in-app purchase simulation will only work on physical devices.
The process of defining an initial value for a property inside your class or structure is called initialization, shortened to init()
.
With this method, you avoid leaving stored properties in an indeterminate state, potentially creating errors and bugs in your code.
For example, if you have a class or a struct to express the value of the United States dollar, you can define its initial value as 1.00:
// for your class
class USD {
var value = Double()
init() {
value = 1.00
}
}
print("USD: \(USD().value)") // output: 1.0
// for your structure
struct USD {
var value = Double()
init() {
value = 1.00
}
}
print("USD: \(USD().value)") // output: 1.0
Your initializer can take parameters, as well.
In this example, the USD will depend on the value of the Euro, using the average exchange rate of 0.93. The result is rounded to avoid having more than two decimal places in instances where the exchange rate isn't a whole-cent value:
// for your class
class USD {
var value = Double()
init(fromEur eur: Double) {
value = eur / 0.93
}
}
print("USD: \(round(USD(fromEur: 0.93).value))") // output: 1.0
// for your structure
struct USD {
var value = Double()
init(fromEur eur: Double) {
value = eur / 0.93
}
}
print("USD: \(round(USD(fromEur: 0.93).value))") // output: 1.0
Swift is capable of handling optional types, which are properties that may or may not contain any value.
However, using them in your code before being certain that they do contain a value can cause crashes and bugs.
Optional binding is a safe mechanism to unwrap those values using an if
statement. This will define two different paths, depending on whether the property contains a value or not, and prevent crashes or bugs in the event that the property contains a nil
value.
For example, in this simple code snippet, we create an optional binding for cash
to see if there’s any cash in our wallet or if we have nil
:
// set your variable
var cash: Int?
// check its value using an if statement
if let quantity = cash {
print("I have \(quantity)$ in my wallet.")
} else {
print("I don't have any money in my wallet.")
}
// set a custom value
cash = 50
// check its value again using an if statement
if let quantity = cash {
print("I just added \(quantity)$ in my wallet.")
} else {
print("I still don't have any money in my wallet.")
}
It’s important that you know how to answer this general-knowledge question about the Swift programming language, as the use of both classes and structures is quite extensive on pretty much every iOS project. The following table clarifies the difference between the two.
Class | Struct | |
---|---|---|
Type | reference type | value type |
Can be manually deinitialized | true | false |
Can inherit from another class | true | false |
The key factor is the type.
As a value type, structures maintain a unique copy of every piece of data you assign to them, while classes don’t.
A structure is like creating a copy of a folder on your desktop, while a class is like creating a link to that folder.
With a structure, you'll have two separate instances of the data, and changing one doesn’t affect the other. With a class, there's a single instance of the data, but two ways to access it. An example in code:
// create a class named phone
class phoneClass {
var brand: String
var model: String
init(brand: String, model: String) {
self.brand = brand
self.model = model
}
}
// create an instance for iPhone X
var iPhoneX = phoneClass(brand: "Apple", model: "iPhone XS")
// create a duplicate for iPhone X
let anotheriPhoneX = iPhoneX
// correct the model of iPhone X
iPhoneX.model = "iPhone X"
// print the result
print(anotheriPhoneX.brand, anotheriPhoneX.model) // output: Apple iPhone X
print(anotheriPhoneX.brand, anotheriPhoneX.model) // output: Apple iPhone X
// create a structure named phone
struct phoneStruct {
var brand = String()
var model = String()
}
// create an instance called iPhone 13
var iPhone13 = phoneStruct(brand: "Apple", model: "iPhone 12 Pro Max")
// create a duplicate for iPhone 13
let iPhone12 = iPhone13
// correct iPhone 13 model
iPhone13.model = "iPhone 13 Pro Max"
// print the result
print(iPhone13.brand, iPhone13.model) // output: Apple iPhone 13 Pro Max
print(iPhone12.brand, iPhone12.model) // output: Apple iPhone 12 Pro Max
It's considered best practice to default to using structures, and using classes only when you need specific features of a class.
For instance, you might use the class type when you need to inherit from another class to create a subclass, or if you require a deinit()
function for deinitialization.
Completion handlers help save you time and resources by notifying you when a task is complete.
For instance, pretend you want to run a second function immediately after another one finishes. Maybe you've made a large network request and want to execute the file after the request is complete.
You could check the network request multiple times to see if it's complete and then execute your next function. Or, a completion handler can automatically let you know when your first function is complete and then immediately move to the next task.
Concurrency, also known as multithreading, helps your iOS app perform better and faster. It takes advantage of asynchronous functions in a multithread code to improve responsiveness and user experience.
One of the best ways to achieve that is through Grand Central Dispatch, or GCD in the Swift programming language. GCD works by assigning the most demanding tasks to the background, so you can safely compile your code from different threads.
Operations and OperationQueues are also a good method to achieve concurrency. They are created on top of GCD, but they also allow implementation of dependencies, as well as re-using, canceling, or suspending operations.
Although their name sounds similar, Array
and NSArray
are two very different types of arrays, and it’s important not to confuse them. Use the table below to memorize the differences and prepare for your interview:
Array | NSArray | |
---|---|---|
Type | value type | reference type |
Content | same types of data | all types of data |
Mutable | true | false |
While NSArray
can contain multiple value types, from String
or Int
to custom types, an Array
is designed to only contain objects of the same type.
The other major difference is that as a reference type, an NSArray
can't be edited after its creation. As a value type, though, an Array
can be modified at any point.
There could be situations where a developer doesn't want a class to be inherited or subclassed. This is most common when the class contains functionality that shouldn't be altered.
You can prevent inheritance by declaring the class as final class
rather than just class
. The final
modifier will cause a compile-time error if you try to subclass a final class
.
It's also important to know that you can prevent inheritance of part of your class instead of the entire class. To do so, you can add the marker final
before your property (final var
) or before your method (final func
).
Swift implements a method to manage memory called Automatic Reference Counting (ARC), which, as it runs, counts the number of references to an object so that when its strong references reach zero, it can be deallocated. This helps free up some memory by not retaining unnecessary data.
If you have properties referencing each other, the memory will not be able to deallocate those properties. This will cause a retain cycle, where properties are retained in the memory because they are constantly referenced. This affects performance and user experience, sometimes causing bugs and crashes.
You can prevent retain cycles by using weak
references. By marking your variable with keyword weak
, you make sure that it will not affect the reference count, so their value can become nil
once there are strong references to it and be deallocated.
The following are the control transfer statements included in iOS Swift:
While weak variables can fix a retain cycle and keep the application’s memory’s load a little lighter, they're optional types, and they may contain no value at all. This implies that they will need unwrapping before use.
That is not true for unowned
references. Although they work in the same way as weak
references in that they don't affect your memory’s ARC, the main difference between them is that an unowned
reference is not optional, and will always contain a value.
The catch is that a referenced instance of an unowned
reference may be deallocated and make your unowned
reference unexpectedly find no value, causing a fatal error. Make sure to only use unowned variables when you are certain that they will contain a value.
Protocols are a collection of properties and methods that define how a class, a structure, or an enumeration should work. The most common examples: UITableViewDelegate
to use table views, UICollectionViewDelegate
to use collection views, and so on.
You can conform to more than one protocol by adding the protocol name separated by a comma:
class myClass: ProtocolOne, ProtocolTwo {
}
Any superclass names should come before the protocol names, as follows:
class myClass: SuperClass, ProtocolOne, ProtocolTwo {
}
Protocols make your code more flexible and reusable, and reduce errors by ensuring that your components conform to their respective protocols.
A ternary conditional operator will convert a block of code into a single line. It's designed to improve readability while saving space in your IDE.
Ternary conditional operators are especially helpful for replacing if-else
statements.
It's not recommended to use this technique for all parts of your code, as it may make it more confusing.
let thumbs = 2
if thumbs > 1 {
print ("Thumbs up.")
}
else {
print ("Thumbs down.")
}
Using ternary conditional operators, this concatenates to:
thumbs > 1 ? print("Thumbs up.") : print ("Thumbs down.")
Interviews can be intense, and the last thing you want is for your mind to go blank while your knowledge is being tested. Especially for a competitive position, you want to be sure to practice and study ahead of time so that you're ready for anything that comes at you.
Whether you have an interview lined up or are still looking for your opening, consider strengthening your skills with Exponent, a multidisciplinary engineering and development learning platform to help you strengthen your Swift knowledge and ensure that you're ready for the big day.
Exponent is the fastest-growing tech interview prep platform. Get free interview guides, insider tips, and courses.
Create your free account