Top Swift Interview Questions for 2025
Question: What is the difference between map
, filter
, and reduce
in Swift?
Answer:
In Swift, map
, filter
, and reduce
are powerful higher-order functions that operate on collections (such as arrays, sets, etc.) and allow you to transform, filter, or combine values in a concise and functional programming style. These functions are commonly used to manipulate data in a functional programming way.
1. map
:
- Purpose: Transforms each element of a collection using a closure and returns a new collection containing the transformed elements.
- Operation: Applies the transformation to every element of the original collection, and returns a new collection of the same size with the transformed values.
Example:
let numbers = [1, 2, 3, 4, 5]
let doubledNumbers = numbers.map { $0 * 2 }
print(doubledNumbers) // Output: [2, 4, 6, 8, 10]
In this example:
- The
map
function takes a closure that doubles each element in the arraynumbers
. - It returns a new array
[2, 4, 6, 8, 10]
.
Key Points:
- Input: A collection (e.g., array) of elements.
- Output: A new collection of the same size, with each element transformed.
- Use Case: When you need to transform each element of a collection into a new form.
2. filter
:
- Purpose: Filters elements from a collection based on a given condition (closure) and returns a new collection containing only the elements that satisfy the condition.
- Operation: Tests each element with a closure. Only the elements that evaluate to
true
are included in the returned collection.
Example:
let numbers = [1, 2, 3, 4, 5, 6]
let evenNumbers = numbers.filter { $0 % 2 == 0 }
print(evenNumbers) // Output: [2, 4, 6]
In this example:
- The
filter
function filters the arraynumbers
by keeping only the even numbers. - It returns the array
[2, 4, 6]
.
Key Points:
- Input: A collection (e.g., array) of elements.
- Output: A new collection containing only the elements that satisfy the condition (i.e., the elements for which the closure returns
true
). - Use Case: When you need to extract a subset of elements from a collection based on some condition.
3. reduce
:
- Purpose: Combines all the elements of a collection into a single value by applying a closure that accumulates a result.
- Operation: Takes two parameters in its closure: an accumulator (which stores the result of previous operations) and the current element. It performs the operation and returns a final accumulated result.
Example:
let numbers = [1, 2, 3, 4, 5]
let sum = numbers.reduce(0) { $0 + $1 }
print(sum) // Output: 15
In this example:
- The
reduce
function starts with an initial value of0
and then adds each element of the arraynumbers
to the accumulator. - It returns the sum
15
(i.e.,0 + 1 + 2 + 3 + 4 + 5
).
Key Points:
- Input: A collection (e.g., array) and an initial starting value (the accumulator).
- Output: A single value, which is the result of combining all the elements of the collection using the closure.
- Use Case: When you need to combine or aggregate all elements of a collection into a single value (e.g., summing numbers, concatenating strings).
Summary of Differences:
Function | Purpose | Input | Output | Use Case |
---|---|---|---|---|
map | Transforms each element of the collection | Collection of elements | A new collection of transformed elements | When you need to apply a transformation to each element |
filter | Filters elements based on a condition | Collection of elements | A new collection containing only the elements that satisfy the condition | When you need to select a subset of elements based on a condition |
reduce | Combines all elements into a single value | Collection of elements and an initial accumulator value | A single value | When you need to accumulate a single result (e.g., sum, product, concatenation) |
Examples with Multiple Functions:
map
, filter
, and reduce
Together:
You can combine these functions to perform more complex operations. For example, suppose you want to double all even numbers in an array and then calculate their sum:
let numbers = [1, 2, 3, 4, 5, 6]
// 1. Filter the even numbers
let evenNumbers = numbers.filter { $0 % 2 == 0 }
// 2. Double each even number
let doubledEvenNumbers = evenNumbers.map { $0 * 2 }
// 3. Calculate the sum of doubled even numbers
let sum = doubledEvenNumbers.reduce(0) { $0 + $1 }
print(sum) // Output: 24 (because 2*2 + 4*2 + 6*2 = 24)
In this example:
filter
selects only the even numbers ([2, 4, 6]
).map
doubles the even numbers ([4, 8, 12]
).reduce
calculates the sum of the doubled numbers (4 + 8 + 12 = 24
).
Conclusion:
map
: Transforms each element in a collection.filter
: Selects elements based on a condition.reduce
: Combines elements into a single accumulated value.
These functions are essential tools in Swift’s functional programming toolkit and are highly useful for processing and manipulating collections.
Question: How is the @escaping
attribute used in Swift?
Answer:
In Swift, the @escaping
attribute is used to indicate that a closure passed as a function parameter can escape the scope of the function it is passed into. In simpler terms, an escaping closure is a closure that is executed after the function that takes it as a parameter has returned, typically because it is stored or called asynchronously.
The primary difference between escaping and non-escaping closures is how and when the closure is executed in relation to the lifetime of the function.
Key Concepts:
-
Non-escaping closures:
- These closures are executed within the function they are passed into, before the function returns.
- Swift assumes closures are non-escaping by default.
- Non-escaping closures cannot be stored or used outside of the function’s scope.
-
Escaping closures:
- These closures escape the function they are passed into and may be executed after the function returns.
- An escaping closure can be stored in a variable or used asynchronously (e.g., in completion handlers or dispatched tasks).
- You need to explicitly mark a closure as
@escaping
if you intend to use it in such a manner.
Syntax of @escaping
:
The @escaping
attribute is applied to a closure parameter in a function declaration. You typically use it when you’re passing closures as completion handlers or performing asynchronous operations.
func performAsyncTask(completion: @escaping () -> Void) {
DispatchQueue.global().async {
// Simulate an asynchronous task
sleep(2)
completion() // The closure is called after the function returns
}
}
In this example:
- The closure passed to the
performAsyncTask
function is marked as@escaping
because it will be called after theperformAsyncTask
function finishes executing. - The closure is stored in the dispatch queue and executed asynchronously, meaning it “escapes” the scope of the
performAsyncTask
function.
Why is @escaping
necessary?
- Swift is designed to prevent memory management issues, such as retain cycles, and ensures that closures are captured properly.
- When a closure escapes, Swift cannot guarantee that the closure will be executed immediately or that it will not outlive the function’s scope.
- By marking the closure as
@escaping
, you are explicitly indicating that the closure will outlive the function call, and Swift will allow it to be stored or executed later.
Example with @escaping
:
class TaskManager {
var tasks = [() -> Void]()
func addTask(task: @escaping () -> Void) {
tasks.append(task) // Store the closure for later use
}
func runTasks() {
for task in tasks {
task() // Execute the stored closures
}
}
}
let manager = TaskManager()
manager.addTask {
print("Task 1 executed")
}
manager.addTask {
print("Task 2 executed")
}
manager.runTasks()
// Output:
// Task 1 executed
// Task 2 executed
In this example:
- The
addTask
method accepts an@escaping
closure and stores it in an array. - Later, the
runTasks
method executes all the stored tasks. The closures “escape” the scope of theaddTask
method and are executed later, which is why they need to be marked as@escaping
.
Key Points:
- Escaping closures are closures that can be executed after the function that takes them as a parameter has returned.
- Non-escaping closures are closures that are executed during the lifetime of the function.
- The
@escaping
attribute is required when a closure is stored or used asynchronously, ensuring proper memory management. - When a closure is marked
@escaping
, it may capture references to variables from its surrounding context, which can lead to retain cycles if not managed carefully.
When to Use @escaping
:
- Asynchronous operations (e.g., when using
DispatchQueue
, network requests, etc.) - Completion handlers that are called after a function returns.
- Storing closures in properties or variables for later execution.
Example: Using @escaping
with an asynchronous network request:
func fetchDataFromServer(completion: @escaping (Data?, Error?) -> Void) {
// Simulate network request
DispatchQueue.global().async {
// Simulating a delay
sleep(2)
let data = Data() // Fake data
completion(data, nil) // The closure escapes the scope and is called after the function returns
}
}
fetchDataFromServer { data, error in
if let data = data {
print("Data received: \(data)")
} else if let error = error {
print("Error: \(error)")
}
}
In this example:
- The closure passed to
fetchDataFromServer
is marked@escaping
because it is called asynchronously after the network operation completes, which occurs outside the scope of the function.
Conclusion:
- Escaping closures are closures that outlive the function they are passed into. They are commonly used for asynchronous operations, callbacks, and completion handlers.
- Swift requires you to mark such closures with
@escaping
to ensure proper memory management and avoid unexpected behavior.
Read More
If you can’t get enough from this article, Aihirely has plenty more related information, such as Swift interview questions, Swift interview experiences, and details about various Swift job positions. Click here to check it out.
Tags
- Swift
- Swift programming
- Optionals
- Memory management
- Automatic Reference Counting
- ARC
- Closures
- Value types
- Reference types
- Structs
- Classes
- Generics
- Protocols
- Error handling
- Functional programming
- Map
- Filter
- Reduce
- Guard
- If let
- Singleton
- IBOutlet
- IBAction
- Super keyword
- Multithreading
- GCD
- Concurrency
- Async
- Await
- Type inference
- Performance optimization
- Swift interview questions
- IOS development
- Swift best practices
- Swift development tips
- Swift programming interview