Contents

Swift Interview Questions and Answers

Theoretical Questions

1. What is the difference between a class and a structure in Swift?

Answer:

  • Class: Reference type, supports inheritance, uses reference counting, mutable properties by default.
  • Structure: Value type, no inheritance, copied on assignment, properties are immutable unless declared with var.

2. Explain the purpose of guard in Swift.

Answer:
guard is used for early exit from a function or loop when a condition is not met. It enhances code readability by avoiding nested code.

				
					func checkAge(_ age: Int?) {
    guard let age = age, age >= 18 else {
        print("You must be at least 18 years old.")
        return
    }
    print("You are eligible.")
}

				
			

3. What is the difference between map, flatMap, and compactMap?

Answer:

  • map: Transforms each element and returns an array of optional values.
  • flatMap: Flattens nested collections or removes nil values when used on optionals.
  • compactMap: Returns a new array with non-nil values after transformation.

4. What are protocol extensions, and how are they useful?

Answer:
Protocol extensions allow you to provide default implementations of methods or properties in protocols, enabling code reuse and avoiding duplicate code across conforming types.

5. Explain what defer does in Swift.

Answer:
defer executes a block of code just before the current scope exits, ensuring cleanup or finalization.

				
					func readFile() {
    let file = openFile("example.txt")
    defer {
        closeFile(file)
    }
    // File operations
}

				
			

Advanced Coding Challenges

6. Write a function to check if a string is a palindrome.

Answer:

				
					func isPalindrome(_ str: String) -> Bool {
    let cleaned = str.lowercased().filter { $0.isLetter }
    return cleaned == String(cleaned.reversed())
}
print(isPalindrome("A man, a plan, a canal, Panama")) // true

				
			

7. Implement a function to remove duplicates from an array.

Answer:

				
					func removeDuplicates<T: Hashable>(_ array: [T]) -> [T] {
    var seen = Set<T>()
    return array.filter { seen.insert($0).inserted }
}
print(removeDuplicates([1, 2, 2, 3, 4, 4])) // [1, 2, 3, 4]

				
			

8. Write a generic function to find the maximum element in an array.

Answer:

				
					func findMax<T: Comparable>(_ array: [T]) -> T? {
    return array.max()
}
print(findMax([3, 1, 4, 1, 5, 9])) // 9

				
			

9. How would you implement a singleton in Swift?

Answer:

				
					class Singleton {
    static let shared = Singleton()
    private init() {}
}
let instance = Singleton.shared

				
			

10. Write a function to calculate the factorial of a number recursively.

Answer:

				
					func factorial(_ n: Int) -> Int {
    return n <= 1 ? 1 : n * factorial(n - 1)
}
print(factorial(5)) // 120

				
			

Advanced Questions

11. What is the difference between weak, strong, and unowned references?

Answer:

  • Strong: Default, increments the reference count.
  • Weak: Does not increment the reference count, allows the reference to become nil.
  • Unowned: Does not increment the reference count, assumes the reference will never be nil.

12. Explain Swift’s type inference. When should you explicitly specify types?

Answer:

  • Type inference automatically determines the type based on the assigned value.
  • Explicit types are useful for clarity or when the value may change in the future.

13. Explain how Combine framework handles asynchronous events and its advantages over traditional patterns like delegation or completion handlers.

Answer:

  • Combine uses publishers and subscribers for reactive programming, enabling chaining and transformation of data streams. It reduces boilerplate and provides a declarative API.
  • Example:
				
					import Combine

let publisher = Just("Hello, Combine!")
let subscription = publisher.sink { print($0) }

				
			

14. How do you handle asynchronous code in Swift?

  • Answer:
    Using GCD, async/await, or operation queues. Example with async/await:
				
					func fetchData() async throws -> String {
    return try await URLSession.shared.data(from: URL(string: "https://example.com")!).0.description
}

				
			

15. What are property wrappers, and how are they used?

Answer:
Property wrappers add behavior to properties. Example:

				
					@propertyWrapper
struct Capitalized {
    private var value: String = ""
    var wrappedValue: String {
        get { value }
        set { value = newValue.capitalized }
    }
}
struct User {
    @Capitalized var name: String
}
var user = User()
user.name = "john"
print(user.name) // John

				
			

Scenario-Based Questions

16. How would you debug retain cycles in a Swift project?

Answer:
Use Xcode’s memory graph debugger or analyze references to detect strong reference cycles. Resolve cycles using weak or unowned.

17. Write a function that reverses an array in place.

Answer:

				
					func reverseArray<T>(_ array: inout [T]) {
    array.reverse()
}
var nums = [1, 2, 3, 4, 5]
reverseArray(&nums)
print(nums) // [5, 4, 3, 2, 1]

				
			

18. What is a Result type, and how is it used in Swift?

Answer:

  • Result encapsulates success and failure cases.
				
					enum MyError: Error { case example }
func divide(_ a: Int, by b: Int) -> Result<Int, MyError> {
    b == 0 ? .failure(.example) : .success(a / b)
}
let result = divide(10, by: 0)
switch result {
case .success(let quotient): print("Quotient: \(quotient)")
case .failure(let error): print("Error: \(error)")
}

				
			

19. How do you implement dependency injection in Swift?

Answer:

  • Use initializer or property injection
				
					protocol Service {
    func fetchData()
}
class APIService: Service {
    func fetchData() { print("Fetching data...") }
}
class Controller {
    private let service: Service
    init(service: Service) { self.service = service }
    func loadData() { service.fetchData() }
}
let controller = Controller(service: APIService())
controller.loadData()

				
			

20. What are the differences between @escaping and @nonescaping closures? In what scenarios would you use each?

Answer:

  • @escaping: The closure is stored and executed after the function scope has ended. Used in asynchronous operations like network requests.
  • @nonescaping: The closure is executed within the function’s scope. Used for immediate, synchronous tasks.