November 22, 2025
12 min read

Junior Mobile Developer (iOS) Interview Questions: Complete Guide

interview
career-advice
job-search
entry-level
Junior Mobile Developer (iOS) Interview Questions: Complete Guide
MB

Milad Bonakdar

Author

Master iOS development fundamentals with essential interview questions covering Swift, UIKit, SwiftUI, data persistence, and iOS app architecture for junior developers.


Introduction

iOS development has become one of the most sought-after skills in mobile app development. With Swift as the primary language and powerful frameworks like UIKit and SwiftUI, iOS developers create experiences for millions of users worldwide.

This guide covers essential interview questions for Junior iOS Developers. We explore Swift fundamentals, UI development, data persistence, networking, and iOS best practices to help you prepare for your first iOS developer role.


Swift Fundamentals (6 Questions)

1. What is the difference between var and let in Swift?

Answer:

  • var: Declares a mutable variable. Its value can be changed after initialization.
  • let: Declares an immutable constant. Once set, its value cannot be changed.
  • Best Practice: Use let by default for safety and clarity. Only use var when you know the value will change.
let name = "John"  // Cannot be changed
var age = 25       // Can be changed
age = 26           // Valid
// name = "Jane"   // Error: Cannot assign to value

Rarity: Very Common Difficulty: Easy


2. Explain Optionals in Swift. What is Optional Binding?

Answer: Optionals represent a value that might be nil (absence of a value).

  • Declaration: Use ? after the type: var name: String?
  • Unwrapping Methods:
    • Force Unwrapping: name! (dangerous, crashes if nil)
    • Optional Binding: Safely unwrap using if let or guard let
    • Nil Coalescing: name ?? "Default"
    • Optional Chaining: user?.address?.city
var email: String? = "user@example.com"

// Optional Binding
if let unwrappedEmail = email {
    print("Email: \(unwrappedEmail)")
} else {
    print("No email")
}

// Guard statement
guard let email = email else { return }
print(email)

Rarity: Very Common Difficulty: Easy


3. What is the difference between a class and a struct in Swift?

Answer:

  • Class: Reference type (stored on heap). Supports inheritance. Passed by reference.
  • Struct: Value type (stored on stack). No inheritance. Passed by copy.
  • When to use:
    • Struct: For simple data models, when you want value semantics (independent copies)
    • Class: When you need inheritance, reference semantics, or deinitializers
struct Point {
    var x: Int
    var y: Int
}

class Person {
    var name: String
    init(name: String) { self.name = name }
}

var p1 = Point(x: 0, y: 0)
var p2 = p1  // Copy created
p2.x = 10    // p1.x is still 0

var person1 = Person(name: "John")
var person2 = person1  // Same reference
person2.name = "Jane"  // person1.name is also "Jane"

Rarity: Very Common Difficulty: Medium


4. What are Closures in Swift?

Answer: Closures are self-contained blocks of functionality that can be passed around and used in your code. They're similar to lambdas or anonymous functions in other languages.

  • Syntax: { (parameters) -> ReturnType in statements }
  • Capturing Values: Closures can capture and store references to variables from their surrounding context.
  • Common Use: Completion handlers, callbacks, array operations.
// Simple closure
let greet = { (name: String) -> String in
    return "Hello, \(name)!"
}
print(greet("Alice"))

// Trailing closure syntax
let numbers = [1, 2, 3, 4, 5]
let doubled = numbers.map { $0 * 2 }
print(doubled)  // [2, 4, 6, 8, 10]

Rarity: Very Common Difficulty: Medium


5. Explain the difference between weak and unowned references.

Answer: Both are used to prevent retain cycles (memory leaks) in reference types.

  • weak: Optional reference that automatically becomes nil when the referenced object is deallocated. Use when the reference might outlive the object.
  • unowned: Non-optional reference that assumes the object will always exist. Crashes if accessed after deallocation. Use when you're certain the reference won't outlive the object.
class Person {
    var name: String
    weak var apartment: Apartment?  // weak to prevent retain cycle
    init(name: String) { self.name = name }
}

class Apartment {
    var unit: String
    unowned let tenant: Person  // unowned - apartment always has tenant
    init(unit: String, tenant: Person) {
        self.unit = unit
        self.tenant = tenant
    }
}

Rarity: Common Difficulty: Medium


6. What are Protocols in Swift?

Answer: Protocols define a blueprint of methods, properties, and other requirements that suit a particular task or piece of functionality. Classes, structs, and enums can adopt protocols.

  • Similar to: Interfaces in other languages
  • Protocol Extensions: Can provide default implementations
  • Protocol-Oriented Programming: Swift's preferred paradigm
protocol Drawable {
    func draw()
}

struct Circle: Drawable {
    var radius: Double
    
    func draw() {
        print("Drawing circle with radius \(radius)")
    }
}

struct Rectangle: Drawable {
    var width: Double
    var height: Double
    
    func draw() {
        print("Drawing rectangle \(width)x\(height)")
    }
}

Rarity: Very Common Difficulty: Easy


UIKit Basics (5 Questions)

7. What is the difference between UIView and UIViewController?

Answer:

  • UIView: Represents a rectangular area on the screen. Handles drawing and event handling. Examples: UILabel, UIButton, UIImageView.
  • UIViewController: Manages a view hierarchy. Handles view lifecycle, user interactions, and navigation. Contains one root view and manages its subviews.
  • Relationship: A view controller manages views. One view controller can have many views.

Rarity: Very Common Difficulty: Easy


8. Explain the View Controller lifecycle methods.

Answer: View controllers have specific lifecycle methods called in order:

  1. viewDidLoad(): Called once when the view is loaded into memory. Setup that needs to happen once.
  2. viewWillAppear(_:): Called before the view appears on screen. Can be called multiple times.
  3. viewDidAppear(_:): Called after the view appears on screen. Start animations here.
  4. viewWillDisappear(_:): Called before the view disappears.
  5. viewDidDisappear(_:): Called after the view disappears. Clean up resources.
override func viewDidLoad() {
    super.viewDidLoad()
    // One-time setup: configure UI, load data
}

override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)
    // Refresh data, start listening to notifications
}

Rarity: Very Common Difficulty: Easy


9. What is Auto Layout and how do you use constraints?

Answer: Auto Layout is a constraint-based layout system that allows you to create adaptive UIs that work across different screen sizes and orientations.

  • Constraints: Define relationships between views (width, height, spacing, alignment)
  • Methods:
    • Interface Builder: Visual editor in Xcode
    • Programmatic: NSLayoutConstraint or NSLayoutAnchor API
    • Visual Format Language: String-based (less common now)
let button = UIButton()
button.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(button)

NSLayoutConstraint.activate([
    button.centerXAnchor.constraint(equalTo: view.centerXAnchor),
    button.centerYAnchor.constraint(equalTo: view.centerYAnchor),
    button.widthAnchor.constraint(equalToConstant: 200),
    button.heightAnchor.constraint(equalToConstant: 50)
])

Rarity: Very Common Difficulty: Medium


10. What is the difference between frame and bounds?

Answer:

  • frame: The view's location and size in its superview's coordinate system. Used for positioning the view.
  • bounds: The view's location and size in its own coordinate system. Origin is usually (0, 0). Used for drawing content inside the view.
  • Key Difference: frame is relative to parent, bounds is relative to itself.
let view = UIView(frame: CGRect(x: 50, y: 100, width: 200, height: 150))
print(view.frame)   // (50, 100, 200, 150) - position in superview
print(view.bounds)  // (0, 0, 200, 150) - own coordinate system

Rarity: Common Difficulty: Medium


11. How do you handle user input from a UITextField?

Answer: Multiple approaches exist:

  • Target-Action: Add target for .editingChanged event
  • Delegate: Conform to UITextFieldDelegate protocol
  • Combine: Use publishers (modern approach)
class ViewController: UIViewController, UITextFieldDelegate {
    let textField = UITextField()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // Delegate approach
        textField.delegate = self
        
        // Target-Action approach
        textField.addTarget(self, action: #selector(textChanged), for: .editingChanged)
    }
    
    @objc func textChanged(_ textField: UITextField) {
        print("Text: \(textField.text ?? "")")
    }
    
    // Delegate method
    func textFieldShouldReturn(_ textField: UITextField) -> Bool {
        textField.resignFirstResponder()
        return true
    }
}

Rarity: Very Common Difficulty: Easy


SwiftUI Basics (4 Questions)

12. What is SwiftUI and how is it different from UIKit?

Answer: SwiftUI is Apple's declarative UI framework introduced in iOS 13.

  • UIKit: Imperative (you tell the system how to build the UI step by step). Mature, widely used.
  • SwiftUI: Declarative (you describe what the UI should look like). Modern, less code, automatic updates.
  • Key Features:
    • Live preview in Xcode
    • Cross-platform (iOS, macOS, watchOS, tvOS)
    • State-driven UI updates
    • Built-in animations
// SwiftUI
struct ContentView: View {
    @State private var count = 0
    
    var body: some View {
        VStack {
            Text("Count: \(count)")
            Button("Increment") {
                count += 1
            }
        }
    }
}

Rarity: Very Common Difficulty: Easy


13. Explain @State, @Binding, and @ObservedObject in SwiftUI.

Answer: These are property wrappers for managing state in SwiftUI:

  • @State: Private state owned by the view. When it changes, the view re-renders.
  • @Binding: Creates a two-way connection to a @State property owned by a parent view.
  • @ObservedObject: References an external object (class conforming to ObservableObject). View updates when the object's @Published properties change.
class UserData: ObservableObject {
    @Published var name = ""
}

struct ParentView: View {
    @State private var isOn = false
    @ObservedObject var userData = UserData()
    
    var body: some View {
        VStack {
            ChildView(isOn: $isOn)  // Pass binding
            Text(userData.name)
        }
    }
}

struct ChildView: View {
    @Binding var isOn: Bool  // Receives binding
    
    var body: some View {
        Toggle("Switch", isOn: $isOn)
    }
}

Rarity: Common Difficulty: Medium


14. How do you create a list in SwiftUI?

Answer: Use the List view to display scrollable collections of data.

struct ContentView: View {
    let fruits = ["Apple", "Banana", "Orange", "Grape"]
    
    var body: some View {
        List(fruits, id: \.self) { fruit in
            Text(fruit)
        }
    }
}

// With custom model
struct Task: Identifiable {
    let id = UUID()
    let title: String
}

struct TaskListView: View {
    let tasks = [
        Task(title: "Buy groceries"),
        Task(title: "Walk the dog")
    ]
    
    var body: some View {
        List(tasks) { task in
            Text(task.title)
        }
    }
}

Rarity: Common Difficulty: Easy


15. What are View Modifiers in SwiftUI?

Answer: View modifiers are methods that create a new view with modified properties. They're chainable.

  • Common Modifiers: .padding(), .background(), .foregroundColor(), .font(), .frame()
  • Order Matters: Modifiers are applied in sequence
Text("Hello, World!")
    .font(.title)
    .foregroundColor(.blue)
    .padding()
    .background(Color.yellow)
    .cornerRadius(10)
    .shadow(radius: 5)

// Custom modifier
struct PrimaryButtonStyle: ViewModifier {
    func body(content: Content) -> some View {
        content
            .padding()
            .background(Color.blue)
            .foregroundColor(.white)
            .cornerRadius(8)
    }
}

extension View {
    func primaryButton() -> some View {
        modifier(PrimaryButtonStyle())
    }
}

Rarity: Common Difficulty: Easy


Data & Networking (5 Questions)

16. How do you make a network request in iOS?

Answer: Use URLSession for networking tasks.

func fetchData() {
    guard let url = URL(string: "https://api.example.com/data") else { return }
    
    let task = URLSession.shared.dataTask(with: url) { data, response, error in
        if let error = error {
            print("Error: \(error)")
            return
        }
        
        guard let data = data else { return }
        
        // Parse JSON
        do {
            let decoder = JSONDecoder()
            let result = try decoder.decode(MyModel.self, from: data)
            print(result)
        } catch {
            print("Decoding error: \(error)")
        }
    }
    
    task.resume()
}

Rarity: Very Common Difficulty: Medium


17. What is Codable in Swift?

Answer: Codable is a type alias for Encodable & Decodable protocols. It allows easy conversion between Swift types and external representations like JSON.

  • Automatic Synthesis: Swift automatically generates encoding/decoding code if all properties are Codable.
  • Custom Keys: Use CodingKeys enum to map different property names.
struct User: Codable {
    let id: Int
    let name: String
    let email: String
}

// Decoding JSON
let json = """
{
    "id": 1,
    "name": "John Doe",
    "email": "john@example.com"
}
""".data(using: .utf8)!

let decoder = JSONDecoder()
let user = try decoder.decode(User.self, from: json)

// Encoding to JSON
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
let jsonData = try encoder.encode(user)

Rarity: Very Common Difficulty: Easy


18. How do you persist data locally in iOS?

Answer: Multiple options for local data persistence:

  • UserDefaults: Simple key-value storage for small amounts of data (settings, preferences)
  • File System: Save files to Documents or Cache directory
  • Core Data: Apple's framework for object graph management and persistence
  • SQLite: Relational database
  • Keychain: Secure storage for sensitive data (passwords, tokens)
// UserDefaults
UserDefaults.standard.set("John", forKey: "username")
let username = UserDefaults.standard.string(forKey: "username")

// File System
let documentsPath = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
let filePath = documentsPath.appendingPathComponent("data.json")

// Write
try data.write(to: filePath)

// Read
let loadedData = try Data(contentsOf: filePath)

Rarity: Very Common Difficulty: Easy


19. What is the difference between synchronous and asynchronous operations?

Answer:

  • Synchronous: Blocks the current thread until the operation completes. Can freeze the UI if run on the main thread.
  • Asynchronous: Doesn't block the thread. Operation runs in the background, and a completion handler is called when done.
  • Main Thread: UI updates must happen on the main thread. Use DispatchQueue.main.async to switch to the main thread.
// Synchronous (BAD for network calls)
let data = try Data(contentsOf: url)  // Blocks until complete

// Asynchronous (GOOD)
URLSession.shared.dataTask(with: url) { data, response, error in
    // This runs on background thread
    
    DispatchQueue.main.async {
        // Update UI on main thread
        self.label.text = "Data loaded"
    }
}.resume()

Rarity: Common Difficulty: Easy


20. What is Grand Central Dispatch (GCD)?

Answer: GCD is Apple's low-level API for managing concurrent operations.

  • Dispatch Queues: FIFO queues that execute tasks serially or concurrently
  • Main Queue: Serial queue for UI updates
  • Global Queues: Concurrent queues with different priorities (background, utility, userInitiated)
// Run on background thread
DispatchQueue.global(qos: .background).async {
    // Heavy computation
    let result = performExpensiveOperation()
    
    // Update UI on main thread
    DispatchQueue.main.async {
        self.label.text = result
    }
}

// Delay execution
DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) {
    print("Executed after 2 seconds")
}

Rarity: Common Difficulty: Medium


Related Posts

Recent Posts

Weekly career tips that actually work

Get the latest insights delivered straight to your inbox