← All blogs
iOS

Top iOS Developer Interview Questions with Answers - Part 2

Swift 6 made strict concurrency checking the default, topics like Actors, Sendable, @MainActor, opaque types, Protocol-Oriented Programming, SOLID, and GraphQL have moved from "nice to have" to "must know."

Anand Gaur
Mobile Tech Lead - Today
Top iOS Developer Interview Questions with Answers - Part 2
Welcome back! If you have already read Part 1, you now have a solid grip on Swift fundamentals, memory management, ARC, closures, GCD, MVVM/VIPER/Clean Architecture, Combine, URLSession, and real-world debugging scenarios.

This Part 2 goes a level deeper and, more importantly, focuses on what interviewers are actually asking in 2026–2027. Since Swift 6 made strict concurrency checking the default, topics like Actors, Sendable, @MainActor, opaque types, Protocol-Oriented Programming, SOLID, and GraphQL have moved from "nice to have" to "must know."

Every answer below is explained in a simple, point-wise, and easy-to-understand way, so whether you are a beginner or a senior engineer, you can follow along and speak about it confidently in interviews.

Note: None of these questions repeat Part 1. This is completely fresh ground.
https://medium.com/@anandgaur2207/top-ios-developer-interview-questions-with-answer-2c4372f93889?sharedUserId=anandgaur2207

Let us begin.


Section 1: Swift Concurrency (Actors, @MainActor, async/await)

This is the number one focus area in modern iOS interviews. Companies want to know if you understand why Swift concurrency exists, not just the syntax.

1) What is async/await and how is it better than completion handlers?

async/await is Swift's modern way to write asynchronous code that reads like normal top-to-bottom code.

In simple words:

  • async marks a function that can pause (suspend) and resume later.
  • await marks the exact point where the function might pause and wait for a result.
  • While it waits, the thread is not blocked. It is freed to do other work.

Old way (completion handler):

func fetchUser(completion: @escaping (User?) -> Void) {
URLSession.shared.dataTask(with: url) { data, _, _ in
let user = try? JSONDecoder().decode(User.self, from: data!)
completion(user)
}.resume()
}

Modern way (async/await):

func fetchUser() async throws -> User {
let (data, _) = try await URLSession.shared.data(from: url)
return try JSONDecoder().decode(User.self, from: data)
}

Why it is better:

  • No nested closures (goodbye “pyramid of doom”).
  • Errors use normal try/catch instead of manual Result handling.
  • No risk of forgetting to call the completion handler.
  • Easier to read, test, and reason about.

2) What is the difference between GCD and Swift Structured Concurrency?

Both handle background work, but they solve the problem very differently.

Feature GCD (DispatchQueue) Structured Concurrency (async/await) Level Low-level, manual High-level, built into the language Thread blocking sync blocks threads Suspends without blocking threads Data race safety No compile-time checks Compiler-enforced (Swift 6) Cancellation Manual and hard Built-in and automatic Task hierarchy None Parent-child task tree

Key point to say in an interview: GCD schedules closures on queues. Structured concurrency creates a tree of tasks where child tasks are automatically cancelled if the parent is cancelled. This prevents leaked, orphaned background work.


3) What is an Actor in Swift and what problem does it solve?

An actor is a reference type (like a class) that protects its own mutable state from data races automatically.

The problem it solves:

  • When multiple threads read and write the same variable at the same time, you get a data race, which causes random crashes and corrupted data.
  • Traditionally we fixed this with locks (NSLock) or serial queues, which are easy to get wrong.

How an actor fixes it:

  • Only one task can access an actor’s mutable state at a time.
  • Swift enforces this at compile time, so races are caught before you even run the app.
actor BankAccount {
private var balance: Int = 0

func deposit(_ amount: Int) {
balance += amount // safe: only one task touches this at a time
}
func getBalance() -> Int {
balance
}
}

Using it: Access from outside must use await, because you may have to wait your turn.

let account = BankAccount()
await account.deposit(100)
let total = await account.getBalance()

Think of it like: A single-lane toilet with a lock. Only one person enters at a time. Everyone else waits in line.


4) What is Actor Reentrancy? (Senior-level trending question)

This is a favorite trick question in 2026.

Simple definition:

  • When an actor method hits an await (a suspension point), the actor is temporarily unlocked.
  • Another task can then “enter” the actor and change its state before your original method resumes.

Why it matters:

  • Any state you read before an await may be stale after the await.
actor ImageLoader {
private var cache: [URL: Image] = [:]

func loadImage(from url: URL) async -> Image {
if let cached = cache[url] { return cached }
let image = await downloadImage(url) // suspension point!
// Danger: another task may have already inserted this url while we waited
cache[url] = image
return image
}
}

How to speak about it: “Actors guarantee no data races, but they do not guarantee that your logic across an await is atomic. Always re-check assumptions after an await."


5) What is @MainActor and why can’t you always just use it everywhere?

@MainActor is a special global actor that guarantees code runs on the main thread.

Why it exists:

  • All UI updates must happen on the main thread.
  • Instead of writing DispatchQueue.main.async { } everywhere, you annotate the type or function.
@MainActor
class ProfileViewModel: ObservableObject {
@Published var name = ""

func updateName(_ new: String) {
name = new // guaranteed on main thread, safe for UI
}
}

Why not put @MainActor on everything?

  • The main thread is responsible for keeping the UI at 60/120 fps.
  • If you force heavy work (parsing, image processing, database queries) onto the main actor, the UI freezes.
  • The correct pattern: run heavy work off the main actor, then hop back to @MainActor only for the final UI update.
func loadData() async {
let data = await heavyBackgroundWork() // off main
await MainActor.run {
self.items = data // back on main, UI safe
}
}

6) What is Sendable and why did Swift 6 make it a compile error? (Hot 2026 topic)

Sendable is a marker protocol that says: "This type is safe to pass between threads, actors, and tasks."

The rules:

  • Value types (struct, enum) are automatically Sendable if all their properties are Sendable.
  • Classes (reference types) are not automatically safe. You must opt in and guarantee safety yourself.
// Struct is automatically Sendable
struct Config: Sendable {
let timeout: Int
let url: URL
}

// A class must manually promise thread safety
final class Cache: @unchecked Sendable {
private let lock = NSLock()
private var store: [String: Data] = [:]
func set(_ value: Data, for key: String) {
lock.lock(); defer { lock.unlock() }
store[key] = value
}
}

Why Swift 6 made it strict:

  • Earlier it was only a warning. Now passing a non-Sendable type across a concurrency boundary is a compile-time error.
  • This eliminates an entire category of hard-to-reproduce data race crashes before shipping.

Migration tip to mention: Enable it gradually with SWIFT_STRICT_CONCURRENCY = targeted before turning on full Swift 6 mode.


7) How do you run multiple async tasks in parallel? (async let vs TaskGroup)

Use async let when you know the exact number of tasks ahead of time (a fixed few).

func loadDashboard() async throws -> Dashboard {
async let profile = fetchProfile() // starts immediately
async let feed = fetchFeed() // starts immediately, in parallel
async let notifications = fetchNotifications()

// Wait for all three together
return try await Dashboard(profile: profile,
feed: feed,
notifications: notifications)
}

Use TaskGroup when the number of tasks is dynamic (for example, a loop over N items).

func loadImages(urls: [URL]) async -> [Image] {
await withTaskGroup(of: Image.self) { group in
for url in urls {
group.addTask { await download(url) }
}
var results: [Image] = []
for await image in group {
results.append(image)
}
return results
}
}

One-line summary: async let = fixed known tasks. TaskGroup = dynamic list of tasks.


8) What is Task cancellation and how do you handle it?

A Task is a unit of asynchronous work. Structured concurrency supports cooperative cancellation.

Cooperative means: Swift does not force-kill your task. Instead, it marks it as cancelled, and your code must check for it and stop.

func processLargeFile() async throws {
for chunk in fileChunks {
try Task.checkCancellation() // throws if cancelled, stops early
await process(chunk)
}
}

Key points:

  • Use Task.isCancelled (returns a Bool) or Task.checkCancellation() (throws).
  • Cancelling a parent task automatically cancels all its child tasks.
  • Always cancel long-running tasks in onDisappear or deinit to avoid wasted work.

9) What is AsyncSequence and how does it compare to Combine?

AsyncSequence lets you receive a stream of values over time using a simple for await loop.

for await message in chatStream {
display(message) // runs each time a new value arrives
}

AsyncSequence vs Combine:

  • AsyncSequence is native to Swift concurrency, simpler syntax, and integrates with async/await.
  • Combine is more mature, has many operators (map, filter, debounce), but is Apple-only and heavier.

Interview tip: For new code, many teams now prefer AsyncSequence + async/await over Combine for simple event streams.


Section 2: Swift Language Features (Tuples & Opaque Types)

10) What is a Tuple in Swift and when should you use it?

A tuple groups multiple values into a single compound value, without creating a struct or class.

let person = (name: "Anand", age: 30)
print(person.name) // Anand
print(person.age) // 30

Common uses:

  • Returning multiple values from a function:
func minMax(_ numbers: [Int]) -> (min: Int, max: Int) {
(numbers.min()!, numbers.max()!)
}
let result = minMax([3, 1, 9])
print(result.min, result.max) // 1 9
  • Decomposing values cleanly:
let (statusCode, message) = (200, "OK")
  • Switch pattern matching:
let point = (0, 5)
switch point {
case (0, 0): print("Origin")
case (0, _): print("On Y axis")
default: print("Somewhere else")
}

When NOT to use tuples:

  • If the group of values is used across many files or has meaning/behavior, use a struct instead.
  • Tuples cannot conform to protocols and have no methods, so they are best for small, local, temporary grouping.

11) What are Opaque Types (some) and why do we need them?

An opaque type, written with the keyword some, lets a function return a specific concrete type without revealing exactly which type it is.

The problem it solves: In SwiftUI, view bodies return extremely complex nested types. Writing them out by hand is impossible.

var body: some View {   // "some View" hides the real, huge type
VStack {
Text("Hello")
Button("Tap") { }
}
}

Key points:

  • some means "there is one specific type here, the compiler knows it, but you the caller do not need to."
  • The returned type must always be the same on every code path.
  • It preserves type identity, so the compiler can still optimize it (fast, static dispatch).

Think of it like: A sealed gift box labeled “a toy inside.” You know it is definitely a toy (one specific type), you just cannot see the brand.


12) What is the difference between some and any? (Very common 2026 question)

This is the question that separates mid-level from senior candidates.

some (Opaque Type):

  • One fixed, specific underlying type, decided at compile time.
  • Uses static dispatch = fast, no runtime overhead.
  • The compiler always knows the real type behind the scenes.

any (Existential Type):

  • Can hold any type that conforms to the protocol, and it can change.
  • Uses type erasure and dynamic dispatch = slower, adds a runtime “box.”
  • Use when you genuinely need to store a mixed collection of different conforming types.
protocol Shape { func area() -> Double }

// some: must be ONE concrete type (all Circles here)
func makeShape() -> some Shape { Circle() }
// any: can be a mix of different types
let shapes: [any Shape] = [Circle(), Square(), Triangle()]

One-liner for interviews: “Use some when the type is fixed and you want performance. Use any when you need flexibility to store different types together, accepting a small runtime cost."


13) What are Swift Macros? (Trending, Swift 5.9+)

A macro generates code at compile time, reducing repetitive boilerplate.

Simple idea: You write a short annotation, and the macro expands into full code automatically before compilation.

@Observable          // a macro that generates observation code for you
class Store {
var count = 0
}

Why interviewers ask: Modern Apple frameworks (@Observable, @Model in SwiftData) are all built on macros. Knowing they run at compile time (not runtime, unlike reflection) shows current knowledge.

Two flavors to mention:

  • Freestanding macros (used with #, like #Preview).
  • Attached macros (used with @, like @Observable).

14) What is a Result Builder (@resultBuilder)?

A result builder lets you build a value step by step using a clean, declarative syntax. It is the magic behind SwiftUI’s view syntax.

var body: some View {
VStack { // these lines are collected by a result builder
Text("Line 1")
Text("Line 2")
}
}

Key point: SwiftUI’s @ViewBuilder is a result builder. It takes those separate Text lines and combines them into one view without you writing commas or arrays. This is why SwiftUI looks so clean.


Section 3: Architecture & Paradigms (POP & SOLID)

15) What is Protocol-Oriented Programming (POP)?

POP is a design approach, promoted by Apple, that says: “Start with protocols instead of base classes.”

Core ideas:

  • Define behavior using protocols (the contract).
  • Add default implementations using protocol extensions.
  • Compose small protocols together instead of building deep inheritance trees.
protocol Greetable {
var name: String { get }
}

extension Greetable {
func greet() { // default behavior for everyone
print("Hello, \(name)!")
}
}
struct User: Greetable {
let name: String
}
User(name: "Anand").greet() // Hello, Anand!

Why Swift loves POP:

  • Structs and enums (value types) cannot inherit, but they can conform to protocols. POP brings shared behavior to them.
  • Avoids the “fragile base class” problem of deep inheritance.
  • Encourages small, testable, composable pieces.

16) What is the difference between POP and OOP (inheritance)?

Aspect OOP (Inheritance) POP (Protocols) Works with Classes only Struct, enum, and class Reuse mechanism Base class Protocol extensions Structure Vertical (deep tree) Horizontal (composition) Flexibility A class has one parent A type can adopt many protocols Coupling Tighter Looser

How to explain : OOP asks “what is this object?” (a Car is a Vehicle). POP asks “what can this object do?” (this thing can Drive, can Honk). A type can do many things, but usually is only one thing.


17) Explain the SOLID Principles with iOS examples.

SOLID is five principles for writing clean, maintainable, testable code. Interviewers love asking you to explain each with a real example.

S — Single Responsibility Principle

  • A type should have only one reason to change (one job).
  • iOS example: Do not let a ViewController fetch data, parse JSON, AND format UI. Split it: ViewModel handles logic, a Service handles networking.

O — Open/Closed Principle

  • Code should be open for extension, closed for modification.
  • iOS example: Instead of adding if type == .email / if type == .sms everywhere, define a NotificationSender protocol and add new senders without touching old code.

L — Liskov Substitution Principle

  • A subclass must be usable anywhere its parent is used, without breaking behavior.
  • iOS example: If Square inherits from Rectangle but breaks width/height logic, it violates LSP. Prefer protocols to avoid this trap.

I — Interface Segregation Principle

  • Do not force a type to implement methods it does not need. Prefer many small protocols over one giant one.
  • iOS example: Instead of one huge Worker protocol, split into Printable, Scannable, Faxable, so a simple printer does not need to implement fax.

D — Dependency Inversion Principle

  • Depend on abstractions (protocols), not concrete types.
  • iOS example: A ViewModel should depend on APIServiceProtocol, not on a concrete URLSession class. This lets you inject a mock for testing.
protocol APIServiceProtocol {
func fetchUsers() async throws -> [User]
}

class ViewModel {
private let service: APIServiceProtocol // depends on abstraction
init(service: APIServiceProtocol) {
self.service = service
}
}

18) What is the Repository Pattern? (Trending in 2026 architecture rounds)

The Repository pattern hides where data comes from. Your ViewModel does not know or care if data is from the network, a local database, or a cache.

protocol UserRepository {
func getUsers() async throws -> [User]
}

// Real implementation
class RemoteUserRepository: UserRepository {
func getUsers() async throws -> [User] { /* call API */ }
}
// Test implementation
class MockUserRepository: UserRepository {
func getUsers() async throws -> [User] { [User(name: "Test")] }
}

Why it matters:

  • Clean separation between UI logic and data logic.
  • Easy to swap network for offline cache.
  • Very easy to unit test using a mock repository.

19) What are the three types of method dispatch in Swift? (Senior-level performance question)

Dispatch = how Swift decides which function implementation to actually run. This directly relates to POP performance.

  • Static (Direct) Dispatch: The compiler knows exactly which function to call at compile time. Fastest. Used for struct, enum, final class, and global functions. Enables inlining.
  • Table (Witness/V-Table) Dispatch: The correct method is looked up in a table at runtime. Used for non-final class methods and protocol methods. Slightly slower.
  • Message Dispatch: The most dynamic. Used for @objc / Objective-C interop and things like KVO. Slowest, but allows swizzling.

Interview gold: “This is why marking a class final or using value types improves performance. It lets Swift use static dispatch instead of table dispatch."


Section 4: Data & Networking (GraphQL, Encryption vs Encoding)

20) What is GraphQL and how is it different from REST?

GraphQL is a query language for APIs where the client asks for exactly the data it needs, no more, no less.

REST problems that GraphQL solves:

  • Over-fetching: REST returns a full object even when you need only two fields.
  • Under-fetching: REST often needs multiple calls (user, then posts, then comments).

How GraphQL differs:

Feature REST GraphQL Endpoints Many (/users, /posts) Usually one (/graphql) Data shape Server decides Client decides Multiple resources Multiple requests One request Versioning /v1, /v2 Evolve the schema, no versions

Example query: The client asks only for name and email:

query {
user(id: "1") {
name
email
}
}

The server returns only those fields.


21) What are the main operations in GraphQL, and how do you use it on iOS?

Three operation types:

  • Query — read data (like GET).
  • Mutation — change data: create, update, delete (like POST/PUT/DELETE).
  • Subscription — receive real-time updates over a live connection (like WebSockets).

On iOS:

  • The most popular client is the Apollo iOS library.
  • Apollo generates type-safe Swift models from your GraphQL schema, so responses are strongly typed.
  • You can also call GraphQL with plain URLSession by sending the query as JSON in a POST body.

Trade-offs to mention:

  • GraphQL gives flexibility and avoids over-fetching.
  • But caching is harder than REST (no simple URL-based caching), and complex queries can strain the server.

22) What is the difference between Encryption, Encoding, and Hashing? (Classic trick question)

Candidates often confuse these three. Explaining all three clearly is a strong signal.

Encoding

  • Purpose: Change data format so it can be safely stored or transferred. NOT for security.
  • Reversible: Yes, by anyone. No key needed.
  • Example: Base64, URL encoding, UTF-8.
  • Use case: Sending image data inside JSON as a Base64 string.

Encryption

  • Purpose: Protect data so only authorized people can read it. This is for security.
  • Reversible: Yes, but only with the correct key.
  • Example: AES (symmetric), RSA (asymmetric).
  • Use case: Protecting a user’s messages or stored tokens.

Hashing

  • Purpose: Create a fixed-size fingerprint to verify integrity. One-way.
  • Reversible: No. You cannot get the original back.
  • Example: SHA-256, bcrypt.
  • Use case: Storing passwords, verifying a file was not tampered with.

One-line summary to say out loud:

  • Encoding = for usability (anyone can reverse it).
  • Encryption = for confidentiality (need a key to reverse it).
  • Hashing = for integrity (cannot be reversed).

23) What is Base64 encoding and why is it not secure?

Base64 converts binary data (like images or files) into plain ASCII text.

let text = "Anand"
let encoded = Data(text.utf8).base64EncodedString() // "QW5hbmQ="
let decoded = String(data: Data(base64Encoded: encoded)!, encoding: .utf8)

Why it is NOT security:

  • There is no key. Anyone can decode QW5hbmQ= back to Anand instantly.
  • It only changes the format, it does not hide the meaning.
  • Never use Base64 to “protect” passwords or tokens. Use Keychain + encryption for that.

24) SwiftData vs Core Data: which one and why? (Very hot 2026 question)

SwiftData (iOS 17+) is Apple’s modern, Swift-native persistence framework that replaces most Core Data boilerplate using macros.

@Model
class Note {
var title: String
var content: String
var createdAt: Date

init(title: String, content: String) {
self.title = title
self.content = content
self.createdAt = .now
}
}

Comparison:

Feature Core Data SwiftData Setup Verbose (container, context) Minimal (@Model macro) Syntax Objective-C era APIs Pure Swift SwiftUI integration Manual Built-in (@Query) Min iOS version Very old iOS 17+ Maturity Battle-tested Newer

How to answer well: “For a new project targeting iOS 17+, I would choose SwiftData for its simplicity and SwiftUI integration. For a large existing app or one supporting older iOS versions, Core Data is still the safer choice. SwiftData is actually built on top of Core Data.”


25) How do you handle inconsistent JSON with Codable? (Common practical question)

Codable assumes JSON keys map 1:1 to your properties. Real APIs are messy, so you customize decoding.

Handling mismatched key names with CodingKeys:

struct User: Decodable {
let id: Int
let name: String

enum CodingKeys: String, CodingKey {
case id
case name = "full_name" // JSON key is "full_name"
}
}

Handling missing or optional fields:

  • Make the property optional (String?) so decoding does not fail when the key is absent.

Handling deep transformations:

  • Implement a custom init(from decoder:) to manually decode, convert types, or provide default values.

Key point: Robust apps never assume the API is perfect. Custom decoding prevents crashes from unexpected server responses.


Section 5: Bonus — Trending 2026 Rapid-Fire Questions

These short, high-frequency questions are appearing again and again in 2026 interviews.

26) @Observable vs ObservableObject: what changed?

  • ObservableObject (older): Any @Published change fires objectWillChange, so every observing view re-renders, even if it did not use the changed property. Wasteful.
  • @Observable (iOS 17+ macro): Tracks exactly which properties each view reads, and re-renders only the views that used the changed property. Faster and less boilerplate.
@Observable
class Store {
var count = 0 // only views reading .count re-render
var username = "" // only views reading .username re-render
}

Bonus: @Observable removes the need for @Published, and you use plain @State for owned objects.


27) @StateObject vs @ObservedObject: what is the classic bug?

  • @StateObject: The view owns and creates the object. It survives view re-renders. Use it where the object is first created.
  • @ObservedObject: The view does not own the object; it is passed in from a parent. Use it for objects created elsewhere.

The classic bug: If you create a view model with @ObservedObject inside a view that re-renders often, the object gets recreated and its state resets every time. The fix is to use @StateObject at the point of creation.


28) Swift Testing vs XCTest?

  • XCTest: The classic framework. Uses XCTAssertEqual, test classes, and setUp/tearDown.
  • Swift Testing (newer): Uses the @Test macro and a single expressive #expect(...) for assertions. Cleaner, supports parameterized tests, and works naturally with async/await.
import Testing

@Test func additionWorks() {
#expect(2 + 2 == 4)
}

How to answer: “Swift Testing is the modern direction, but XCTest is still widely used, especially for UI tests. Knowing both shows I can work in old and new codebases.”


29) What is NavigationStack and why did it replace NavigationView?

NavigationStack (iOS 16+) is the modern SwiftUI navigation container that supports type-safe, programmatic navigation.

@Observable
class Router {
var path = NavigationPath()
func goToDetail(_ item: Item) { path.append(item) }
func goBack() { path.removeLast() }
}

Why it is better than NavigationView:

  • Programmatic control: Push and pop screens from code, not just taps.
  • Deep linking: Easily jump to a specific screen from a URL or notification.
  • Type-safe and testable navigation paths.

30) What is the difference between a Task and a detached Task?

  • Task { }: Inherits the priority, actor context, and task-local values of where it was created. This is the default and usually what you want.
  • Task.detached { }: Inherits nothing. It starts fresh with no parent context.

Rule of thumb: Prefer plain Task. Only use Task.detached when you deliberately want to break away from the current actor or priority, which is rare. Overusing detached loses the safety benefits of structured concurrency.


Section 6: Advanced Swift Language Deep Dive

31) What are Generics and why are they useful?

Generics let you write one flexible piece of code that works with many types, without duplicating it.

func swapValues<T>(_ a: inout T, _ b: inout T) {
let temp = a
a = b
b = temp
}

Constraints and where clauses let you restrict which types are allowed:

func findMax<T: Comparable>(_ items: [T]) -> T? {
items.max()
}

// Only run when Element is Equatable
extension Array where Element: Equatable {
func hasDuplicates() -> Bool { /* ... */ false }
}

Why interviewers care: Generics give you type safety + reusability + performance (static dispatch), unlike using Any, which loses type information.


32) What is an associatedtype in a protocol?

An associatedtype is a placeholder type inside a protocol, decided later by whichever type conforms to it.

protocol Container {
associatedtype Item
func add(_ item: Item)
func get(at index: Int) -> Item
}

struct IntBox: Container {
func add(_ item: Int) { } // Item becomes Int here
func get(at index: Int) -> Int { 0 }
}

Key point: A protocol with an associatedtype (or Self requirement) cannot be used directly as a type like let x: Container. You must use it as a generic constraint or with any/some. This is why Equatable and Hashable are often used as constraints, not as plain types.


33) What is Type Erasure and when do you need it?

Type erasure hides a complex or generic type behind a simpler public wrapper.

Why it is needed: Protocols with associatedtype cannot be stored in an array directly. Type erasure wraps them so they can.

Apple’s built-in example: AnyView in SwiftUI erases the specific view type.

Custom example:

struct AnyContainer<Item>: Container {
private let _add: (Item) -> Void
init<C: Container>(_ container: C) where C.Item == Item {
_add = container.add
}
func add(_ item: Item) { _add(item) }
}

Interview tip: Mention that Any prefixes in Apple frameworks (AnyView, AnyPublisher, AnyHashable) are all type-erased wrappers.


34) How do you create a custom Property Wrapper?

A property wrapper adds reusable logic around how a property is read and written.

@propertyWrapper
struct Capitalized {
private var value = ""
var wrappedValue: String {
get { value }
set { value = newValue.capitalized }
}
}

struct User {
@Capitalized var name: String
}
var u = User()
u.name = "anand"
print(u.name) // "Anand"

The $ projected value: If a wrapper exposes a projectedValue, you access it with $. This is why $isOn in SwiftUI gives you a Binding instead of the raw Bool.


35) Explain Enums with associated values, raw values, and indirect.

Raw values = a fixed backing value per case:

enum Direction: String {
case north = "N", south = "S"
}

Associated values = attach different data to each case:

enum NetworkResult {
case success(data: Data)
case failure(error: Error)
}

indirect = allows an enum to reference itself (recursive), useful for trees:

indirect enum Tree {
case leaf(Int)
case node(Tree, Tree)
}

Why enums are powerful in Swift: They can model exclusive states safely. A great interview line: “I use enums to make invalid states unrepresentable.”


36) What are KeyPaths in Swift?

A KeyPath is a reference to a property, not its value. It lets you pass “which property” as an argument.

struct Person { let name: String; let age: Int }

let people = [Person(name: "A", age: 30), Person(name: "B", age: 25)]
// Sort by a property using its keypath
let sorted = people.sorted { $0.age < $1.age }
let names = people.map(\.name) // ["A", "B"] using keypath

Where you see it: SwiftUI (id: \.self), Combine (assign(to: \.value)), and generic sorting/filtering utilities.


37) What is @autoclosure?

@autoclosure automatically wraps an expression into a closure, so it is only evaluated when actually used (lazy evaluation).

func logIfNeeded(_ message: @autoclosure () -> String, enabled: Bool) {
if enabled { print(message()) } // built only if enabled
}

logIfNeeded(expensiveDebugString(), enabled: false) // not evaluated

Real example: assert(condition) and the ?? operator use @autoclosure so the right-hand side is only computed when needed.


38) What is an inout parameter?

inout lets a function modify the caller's original variable, not a copy.

func addTax(to price: inout Double) {
price *= 1.18
}

var amount = 100.0
addTax(to: &amount) // note the & symbol
print(amount) // 118.0

Key points:

  • You must pass with &.
  • It works via “copy-in, copy-out,” not true pass-by-reference.
  • You cannot pass a let constant or literal as inout.

39) What is Copy-on-Write (CoW)?

Copy-on-Write is an optimization where a value type is only actually copied when you modify it.

Simple idea:

  • When you assign an Array to a new variable, Swift does not copy the whole array immediately.
  • Both variables share the same underlying storage.
  • The real copy happens only when one of them is mutated.
var a = [1, 2, 3]
var b = a // no copy yet, shared storage
b.append(4) // NOW a real copy happens

Why it matters: This is how Swift keeps value types (Array, String, Dictionary) both safe (independent copies) and fast (no wasteful copying). Interviewers love when you mention CoW for performance discussions.


40) Explain throws, rethrows, and defer.

throws — the function can throw an error, caller must handle it with try.

rethrows — the function only throws if a closure passed to it throws. Used by higher-order functions like map.

func perform(_ action: () throws -> Void) rethrows {
try action()
}

defer — schedules code to run when the current scope exits, no matter how (return, throw, or normal end). Great for cleanup.

func readFile() throws {
let file = openFile()
defer { file.close() } // always runs, even on error
try process(file)
}

41) How does Swift synthesize Equatable, Hashable, Comparable, and CaseIterable?

Swift can auto-generate conformances so you write less code.

  • Equatable: Auto-generated if all properties are Equatable. You get == for free.
  • Hashable: Auto-generated if all properties are Hashable. Needed for Set and Dictionary keys.
  • CaseIterable: Gives you .allCases for enums without associated values.
  • Comparable: Enums get automatic ordering based on declaration order (Swift 5.3+).
enum Priority: Comparable, CaseIterable {
case low, medium, high
}
Priority.low < Priority.high // true
Priority.allCases // [.low, .medium, .high]

Section 7: More Swift Concurrency (Advanced)

42) How do you convert an old completion-handler API to async/await?

Use a continuation to bridge callback-based code into async/await.

func fetchUser() async throws -> User {
try await withCheckedThrowingContinuation { continuation in
oldFetchUser { result in
switch result {
case .success(let user): continuation.resume(returning: user)
case .failure(let error): continuation.resume(throwing: error)
}
}
}
}

Critical rule to mention: You must call resume exactly once. Calling it zero times leaks the task forever; calling it twice crashes.


43) What is a @Sendable closure?

A @Sendable closure is one that is safe to run concurrently on a different thread or task.

Rules it must follow:

  • It cannot capture mutable non-Sendable state.
  • Any captured values must themselves be Sendable.
func run(_ work: @Sendable @escaping () -> Void) {
Task { work() }
}

Why it exists: It lets the compiler guarantee that the closure will not cause a data race when passed across concurrency boundaries.


44) What does the nonisolated keyword do?

nonisolated marks a property or method inside an actor as not needing actor protection, so it can be accessed without await.

actor User {
let id: String // immutable
var name: String = ""

nonisolated var description: String {
"User \(id)" // only touches immutable id, safe without await
}
}

Use it for: Constants and computed values that do not touch mutable actor state. It improves performance by skipping unnecessary suspension.


45) What is the difference between a data race and a race condition?

These sound similar but are different.

  • Data race: Two threads access the same memory at the same time, and at least one is writing, with no synchronization. This is what actors and Sendable prevent.
  • Race condition: The correctness of your program depends on timing/order of events. You can have a race condition even without a data race.

Key line for interviews: “Swift 6 concurrency eliminates data races at compile time, but it does not automatically eliminate race conditions. Those are a logic problem you still have to design around.”


46) Actor vs a serial DispatchQueue: what is the difference?

Both serialize access to shared state, but:

Feature Serial DispatchQueue Actor Language support Library (GCD) Built into the language Blocking sync blocks a thread Suspends, no blocking Compile-time safety None Enforced isolation Reentrancy Not reentrant Reentrant (be careful)

Summary: An actor is the modern, compiler-checked replacement for the classic “serial queue to protect shared state” pattern.


47) What are Task priorities and why should you set them?

Tasks can be given a priority hint so the system schedules important work first.

Task(priority: .high) { await loadCriticalData() }
Task(priority: .background) { await syncAnalytics() }

Common priorities: .high.userInitiated.medium.utility.background.

Key point: Priority is a hint, not a guarantee. Use .background for non-urgent work (analytics, prefetching) so it does not compete with UI-critical tasks.


Section 8: SwiftUI Deep Dive

48) What is @Binding and when do you use it?

@Binding creates a two-way connection to a piece of state owned by a parent view. The child can read and write it, but does not own it.

struct Parent: View {
@State private var isOn = false
var body: some View {
ToggleRow(isOn: $isOn) // pass a binding with $
}
}

struct ToggleRow: View {
@Binding var isOn: Bool // child edits parent's state
var body: some View {
Toggle("Enable", isOn: $isOn)
}
}

One-liner: @State owns the data. @Binding borrows it for two-way editing.


49) What is the difference between @Environment and @EnvironmentObject?

  • @EnvironmentObject: Injects a shared observable object (like a session or cart) into the view tree, so deeply nested views can access it without passing it manually through every level.
  • @Environment: Reads system or custom environment values like color scheme, dismiss action, or locale.
@EnvironmentObject var cart: CartModel        // shared data object
@Environment(\.colorScheme) var colorScheme // system value
@Environment(\.dismiss) var dismiss // dismiss the screen

Modern note: With the @Observable macro (iOS 17+), you now use @Environment(MyModel.self) instead of @EnvironmentObject.


50) What is View Identity and why does @State sometimes reset unexpectedly?

SwiftUI tracks each view by its identity. If the identity changes, SwiftUI treats it as a brand new view and resets its @State.

Common cause of bugs:

  • Giving a view a new .id(...) on every render recreates it and wipes its state.
  • Structural identity changes (moving a view in/out of an if) can also reset state.

Interview point: “State is tied to a view’s identity. If you want state to persist, keep the view’s identity stable. If you want a fresh start, change its .id() deliberately."


51) What is GeometryReader and what is its downside?

GeometryReader gives you the size and position of the available space, so you can build responsive layouts.

GeometryReader { proxy in
Text("Width is \(proxy.size.width)")
}

Downside to mention: GeometryReader greedily takes all available space and can break your layout if overused. Prefer it only when you truly need measurements, and consider the newer Layout protocol (iOS 16+) for complex custom layouts.


52) What is a PreferenceKey used for?

A PreferenceKey lets a child view pass data UP to its parent (the opposite of the normal top-down data flow).

Use cases:

  • Reporting a child’s size back to the parent.
  • Building a custom sticky header that reacts to scroll position.

Interview point: SwiftUI data usually flows down (parent to child). PreferenceKey is the mechanism to send information up the tree.


53) How do you create a custom ViewModifier?

A ViewModifier packages reusable styling into one reusable component.

struct CardStyle: ViewModifier {
func body(content: Content) -> some View {
content
.padding()
.background(Color.white)
.cornerRadius(12)
.shadow(radius: 4)
}
}

extension View {
func cardStyle() -> some View { modifier(CardStyle()) }
}
// Usage
Text("Hello").cardStyle()

Why it is good: Keeps styling consistent and DRY across the app.


54) Why should you avoid AnyView in SwiftUI?

AnyView erases a view's type. It is sometimes needed, but overusing it hurts performance.

Problems:

  • SwiftUI can no longer see the concrete type, so it cannot optimize diffing efficiently.
  • It can cause more re-renders and lost animations.

Better alternatives: Use @ViewBuilder, Group, or return some View with conditional logic instead of wrapping everything in AnyView.


55) What is the difference between .task and .onAppear?

  • .onAppear: Runs synchronous code when the view appears. You must manually manage any async work and cancellation.
  • .task: Runs async code tied to the view's lifecycle, and automatically cancels the task when the view disappears.
.task {
await viewModel.loadData() // auto-cancelled on disappear
}

Best practice: Prefer .task for async loading. It is safer because it handles cancellation for you.


56) LazyVStack vs VStack: when to use which?

  • VStack: Builds all its child views immediately. Fine for a small, fixed number of items.
  • LazyVStack: Builds children only when they scroll into view. Use it inside a ScrollView for long lists to save memory and improve performance.

One-liner: Small fixed content = VStack. Long scrolling content = LazyVStack (or List).


Section 9: Testing (Modern Practices)

57) What are Test Doubles (Mock, Stub, Spy, Fake)?

Test doubles are fake versions of real dependencies used in tests.

  • Stub: Returns fixed, canned data. (“Always return these 3 users.”)
  • Mock: Verifies that a method was called (and how). (“Was save() called once?")
  • Spy: Records what happened so you can assert on it later.
  • Fake: A lightweight working implementation (like an in-memory database).

Interview tip: Knowing the difference between a stub (provides data) and a mock (verifies behavior) is a strong senior signal.


58) How do you make code testable in iOS?

The key is Dependency Injection through protocols.

protocol APIServiceProtocol {
func fetchUsers() async throws -> [User]
}

class ViewModel {
private let service: APIServiceProtocol
init(service: APIServiceProtocol) { self.service = service }
}
// In tests, inject a mock
let vm = ViewModel(service: MockAPIService())

Why: By depending on a protocol, you can inject a mock in tests instead of hitting the real network. This makes tests fast, reliable, and offline.


59) How do you test async/await code?

Modern test frameworks support async tests directly.

// XCTest
func testFetchUsers() async throws {
let users = try await sut.fetchUsers()
XCTAssertEqual(users.count, 3)
}

// Swift Testing
@Test func fetchUsers() async throws {
let users = try await sut.fetchUsers()
#expect(users.count == 3)
}

Key point: No more XCTestExpectation and wait(for:) gymnastics for most async code. Just mark the test async and await directly.


60) What is the difference between Unit Tests and UI Tests?

  • Unit Tests: Test a single piece of logic (a function, a ViewModel) in isolation. Fast, run in milliseconds, no UI.
  • UI Tests: Simulate real user taps and swipes through the actual app interface. Slower, but verify the end-to-end flow.

Best practice (test pyramid): Write many fast unit tests, fewer integration tests, and only a handful of critical UI tests.


61) What is Snapshot Testing?

Snapshot testing captures an image of a view and compares it against a saved reference image.

How it works:

  • First run saves a “golden” reference image.
  • Later runs fail if the UI visually changes from that reference.

Why teams use it: It catches accidental UI regressions (a broken layout, wrong color) that logic tests would miss.


Section 10: Performance & Memory Optimization

62) Which Instruments tools do you use and for what?

Instruments is Xcode’s profiling suite. Know the main tools:

  • Time Profiler: Finds which functions consume the most CPU time (fixes slowness/lag).
  • Allocations: Tracks memory usage and object counts.
  • Leaks: Detects memory that is never released (retain cycles).
  • Core Animation / Hitches: Finds dropped frames and janky scrolling.
  • Energy Log: Finds battery-draining code.

Interview tip: Naming the right tool for the right problem (Time Profiler for CPU, Leaks for retain cycles) shows real hands-on experience.


63) How do you reduce app launch time?

App launch happens in two phases: pre-main (before your code runs) and post-main.

Optimization points:

  • Reduce the number of dynamic frameworks (they slow dyld loading).
  • Defer heavy work out of application(didFinishLaunching:); do it lazily later.
  • Avoid blocking the main thread during launch.
  • Use lazy initialization for services not needed immediately.

Goal: Apple recommends launching in under ~400ms for a smooth first impression.


64) How do you handle large images to avoid memory crashes?

The trick is downsampling: decode the image at the display size, not the full resolution.

Why it matters:

  • A 4000x3000 photo shown in a 100x100 thumbnail still loads all those pixels into memory if not downsampled.
  • Loading many full-size images is a top cause of memory-related crashes.

Solution: Use CGImageSourceCreateThumbnailAtIndex (or a library like Nuke/Kingfisher) to decode a smaller version sized for the UI.


65) What is an autoreleasepool and when do you need one?

An autoreleasepool releases temporary objects immediately instead of waiting for the run loop.

When to use it: Inside a tight loop that creates many temporary objects, to keep the memory peak low.

for i in 0..<100_000 {
autoreleasepool {
let image = processImage(i) // released each iteration
save(image)
}
}

Without it: All those temporary objects pile up until the loop ends, spiking memory.


66) What is the difference between a hang, a hitch, and a crash?

  • Hitch: A single dropped frame during animation or scrolling. Causes visible stutter.
  • Hang: The main thread is blocked, so the UI is frozen and unresponsive for a noticeable time.
  • Crash: The app terminates entirely (out of memory, force unwrap of nil, etc.).

Root cause of hangs/hitches: Almost always too much work on the main thread. The fix is moving heavy work to a background task.


Section 11: Security & Networking (Advanced)

67) What is SSL / Certificate Pinning and why use it?

Certificate pinning means your app only trusts a specific, known server certificate, not just any certificate the system trusts.

Why: It prevents man-in-the-middle (MITM) attacks, where an attacker uses a fake certificate to intercept your network traffic.

How: In URLSessionDelegate, compare the server's certificate (or public key hash) against a copy bundled in your app, and reject the connection if it does not match.

Trade-off: Very secure, but you must update the app when the server certificate rotates, or connections break.


68) How do you implement Face ID / Touch ID authentication?

Use the LocalAuthentication framework with LAContext.

import LocalAuthentication

let context = LAContext()
var error: NSError?
if context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &error) {
context.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics,
localizedReason: "Log in to your account"
) { success, _ in
if success { /* grant access on main thread */ }
}
}

Key points:

  • Add the NSFaceIDUsageDescription key in Info.plist.
  • Never store the biometric data yourself; iOS handles it in the Secure Enclave.
  • Provide a passcode fallback for reliability.

69) What is App Transport Security (ATS)?

ATS is an Apple security feature that forces network connections to use secure HTTPS by default.

Key points:

  • Blocks plain, insecure HTTP connections.
  • Requires TLS 1.2 or higher.
  • You can add exceptions in Info.plist, but Apple discourages it and may question it during App Review.

Interview line: “ATS makes HTTPS the default. Any HTTP exception needs a strong justification.”


70) How do you implement retry with exponential backoff?

When a network request fails, do not retry instantly in a tight loop. Wait longer after each failure, and add randomness (jitter).

The idea:

  • 1st retry after ~1s, 2nd after ~2s, 3rd after ~4s (doubling).
  • Add jitter (a small random delay) so thousands of devices do not all retry at the exact same moment (the “thundering herd” problem).
  • Cap the maximum delay and the number of retries.
func retryDelay(attempt: Int) -> Double {
let base = pow(2.0, Double(attempt)) // 2, 4, 8...
let jitter = Double.random(in: 0...0.5)
return min(base + jitter, 30) // cap at 30s
}

71) How do you use WebSockets in iOS?

Use URLSessionWebSocketTask for real-time, two-way communication (chat, live prices).

let task = URLSession.shared.webSocketTask(with: url)
task.resume()

// Send
task.send(.string("Hello")) { _ in }
// Receive
task.receive { result in
// handle incoming message, then call receive again to keep listening
}

Key point: WebSockets keep a persistent connection open, unlike REST which opens a new request each time. Better for continuous real-time updates than repeated polling.


72) What is URLCache and how does caching work in URLSession?

URLCache stores network responses so repeated requests can be served from cache instead of the network.

Key points:

  • Respects HTTP cache headers (Cache-Control, ETag) from the server.
  • You control behavior with URLRequest.cachePolicy (for example, .returnCacheDataElseLoad).
  • Reduces data usage, speeds up the app, and enables basic offline reads.

Interview line: “Good caching improves speed and reduces server load, but you must respect cache headers to avoid showing stale data.”


Section 12: iOS System Design & Release

73) How would you design an image caching system?

A common system design question. Use a two-layer cache.

Design:

  • Memory cache (NSCache): Fast, holds recently used images, auto-evicts under memory pressure.
  • Disk cache: Persists images across launches for offline use.
  • Flow: Check memory → then disk → then download from network → then store in both.

Extra points to mention:

  • Downsample images to display size before caching.
  • Use the URL as the cache key.
  • Cancel in-flight downloads when a cell is reused (important in table/collection views).

74) How do you implement pagination (infinite scroll)?

Pagination loads data in small pages instead of all at once.

Approaches:

  • Offset-based: Request page=2&limit=20. Simple, but can skip/duplicate items if data changes.
  • Cursor-based: Server returns a “next cursor” token. More reliable for live-changing data.

Trigger point: Load the next page when the user scrolls near the bottom (for example, when the 5th-from-last item appears via onAppear).

Interview point: Always handle loading state, end-of-list state, and errors for a smooth experience.


75) How do you design an offline-first app?

Offline-first means the app works even without internet, then syncs later.

Core ideas:

  • Local database is the source of truth (SwiftData / Core Data). The UI always reads from local storage.
  • Network updates the local store in the background.
  • Sync strategy: Queue user changes locally, then upload when connectivity returns.
  • Handle conflict resolution (for example, “last write wins” or merge logic) when local and server data differ.

Result: The user sees instant data and never a blank screen, even offline.


76) What is code signing? Explain certificates and provisioning profiles.

Code signing proves an app comes from a trusted developer and was not tampered with.

The pieces:

  • Certificate: Identifies who you are (the developer/team). Development vs Distribution.
  • App ID: A unique identifier for your app.
  • Provisioning Profile: Ties together the certificate + App ID + allowed devices + capabilities. It tells the device “this app is allowed to run.”

Simple analogy: The certificate is your passport (who you are), and the provisioning profile is your visa (permission to run in a specific place).


77) What is a dSYM file and why is it important for crash reports?

A dSYM (debug symbol) file maps the compiled machine addresses back to readable function names and line numbers.

Why it matters:

  • Crash reports from users contain memory addresses, not readable code.
  • Symbolication uses the matching dSYM to turn those addresses into “crashed in LoginViewModel.swift, line 42."
  • Without the correct dSYM, crash logs are useless.

Tip: Always archive and store dSYMs for every release (crash tools like Firebase Crashlytics need them).


78) What is CI/CD in iOS and which tools are used?

CI/CD automates building, testing, and releasing your app.

  • CI (Continuous Integration): Every code push automatically builds the app and runs tests, catching bugs early.
  • CD (Continuous Delivery): Automatically distributes builds to TestFlight or the App Store.

Common tools: Fastlane (automates signing, screenshots, and uploads), Xcode Cloud (Apple’s native CI/CD), plus GitHub Actions, Bitrise, and Jenkins.

Interview value: Mentioning that CI/CD reduces manual release errors and speeds up delivery shows senior, team-level thinking.


79) What is App Thinning?

App Thinning delivers only the resources a specific device needs, keeping downloads small.

Three parts:

  • App Slicing: The App Store delivers only the assets (image resolutions, etc.) for that exact device.
  • On-Demand Resources: Download certain assets later, only when needed (like level 5 of a game).
  • Bitcode (now largely deprecated): Previously let Apple re-optimize the binary.

Result: Smaller installs, faster downloads, less storage used.


80) How would you architect a large, scalable iOS app?

A senior-level open-ended question. Speak about structure and trade-offs, not just code.

Key points to cover:

  • Modularization: Split features into separate Swift Package modules so teams work independently and builds are faster.
  • Clear layers: Presentation (SwiftUI + ViewModel), Domain (business logic), Data (repositories + network/DB).
  • Dependency Injection via protocols for testability.
  • Unidirectional data flow (or a pattern like MVVM / TCA) for predictable state.
  • Navigation handled by a router/coordinator, decoupled from views.
  • Testing and CI/CD baked in from the start.

Winning line: “I optimize for testability, team scalability, and clear boundaries, and I choose patterns based on the team size and product complexity, not hype.”


Summary

If Part 1 built your foundation, Part 2 sharpens the edge that senior interviews test in 2026. Across these 80 questions, we covered Swift concurrency (Actors, @MainActor, Sendable, continuations, structured concurrency), advanced language features (generics, some vs any, type erasure, KeyPaths, Copy-on-Write, macros), clean architecture (POP, SOLID, Repository pattern, dispatch), SwiftUI deep dive (@Binding, view identity, custom modifiers, performance), testing, memory and performance profiling, security and networking (SSL pinning, biometrics, backoff, WebSockets), and iOS system design (image caching, offline-first, code signing, CI/CD).

A quick tip from real interviews: do not just memorize what something is. Always be ready to explain why it exists and the trade-offs. That is what separates a strong hire from an average one.

Best of luck with your interviews.


Level Up Your Mobile Developer Interview !

Cracking the Mobile System Design Interview Book

Your complete practical guide to mastering Mobile System Design Interviews — covering scalable architecture, Android & iOS system design concepts, high-level design strategies, low-level design patterns, performance optimization, offline-first architecture, real-world case.
👉 Grab your copy now:
https://medium.com/@anandgaur2207/cracking-the-mobile-system-design-interview-book-8ff043db0359

iOS Developer Interview Handbook

From Swift fundamentals to advanced iOS concepts — a complete handbook to help you prepare smartly and confidently.
👉 Explore the book:
https://medium.com/@anandgaur2207/crack-ios-developer-interviews-with-confidence-the-complete-ios-developer-handbook-f1eabc3d7a21

Mastering AI for Android Developers

Your complete hands-on guide to integrating AI into Android apps — covering Generative AI, LLMs, on-device intelligence, AI APIs, real-world use cases, and practical implementation with modern Android development.
👉 Grab your copy now:
https://medium.com/@anandgaur2207/mastering-ai-for-android-developers-5cc6d62e7d21

Crack Android Interviews Like a Pro

Your complete Android interview preparation book — packed with real questions, deep explanations, and practical insights to help you stand out.
👉 Grab your copy now:
https://medium.com/@anandgaur2207/crack-android-interviews-with-confidence-the-only-handbook-youll-need-b87ec525f19c

Flutter Developer Interview Handbook

Ace your next Flutter interview with scenario-based questions, detailed explanations, and hands-on examples that make you stand out.
👉 Explore the book:
https://medium.com/@anandgaur2207/crack-flutter-developer-interviews-with-confidence-the-complete-flutter-developer-interview-6cb53996832c

React Native Developer Interview Handbook

Crack your next React Native interview with confidence!
This guide is packed with scenario-based questions, detailed explanations, and hands-on examples to help you stand out and succeed.
👉 Explore the book:
https://medium.com/@anandgaur2207/react-native-interview-crack-your-next-interview-with-confidence-0d7255a20fe1

Need 1:1 Career Guidance or Mentorship?

If you’re looking for personalized guidance, interview preparation help, or just want to talk about your career path in mobile development — you can book a 1:1 session with me on Topmate.

🔗 Book a session here

I’ve helped many developers grow in their careers, switch jobs, and gain clarity with focused mentorship. Looking forward to helping you too!

Found this helpful? Don’t forgot to clap 👏 and follow me for more such useful articles about Android development and Kotlin or buy us a coffee here

If you need any help related to Mobile app development. I’m always happy to help you.

Follow me on:

LinkedIn, Github, Instagram , YouTube & WhatsApp

#iOS#AI#iOS Devloper#iOS Interview