Top 60 Swift Interview Questions (2026)

The 60 Swift questions interviewers actually ask, with direct answers, runnable code, and what the interviewer is listening for. Grouped for freshers, intermediate, and experienced iOS rounds.

60 questions with answers

What Is Swift?

Key Takeaways

  • Swift is Apple's statically typed, compiled language for iOS, macOS, watchOS, tvOS, and increasingly server and cross-platform code.
  • It's built for safety: optionals model the absence of a value, and the compiler catches whole classes of null and type errors before you run.
  • Interviews test how well you understand optionals, value versus reference types, memory (ARC), and protocol-oriented design, not just syntax.
  • This page is a question bank: work your tier first, read one tier up, and say answers out loud because delivery gets evaluated too.

Swift is a statically typed, compiled programming language Apple introduced at WWDC 2014 and open-sourced in 2015. It replaced Objective-C as the primary language for Apple platforms and now runs on Linux and Windows too, so the same language covers iOS apps and server backends. Its defining trait is safety: optionals force you to handle missing values, the type system is strict, and automatic reference counting (ARC) manages memory without a stop-the-world garbage collector. In interviews, Swift questions probe understanding of optionals, value versus reference semantics, memory management, and protocol-oriented design, not memorized keywords. This page collects the 60 questions that come up most, each with a direct answer and runnable code. Apple's official guide, The Swift Programming Language, is the canonical reference; increasingly the first Swift round is a live AI coding interview, so pair this bank with our AI interview preparation guides for the format side.

60Questions with answers on this page
3Difficulty tiers: freshers, intermediate, experienced
2014Year Apple introduced Swift at WWDC
45-60 minTypical length of a Swift technical round

Watch: Swift Tutorial - Full Course for Beginners

Video: Swift Tutorial - Full Course for Beginners (freeCodeCamp.org, YouTube)

Test yourself and earn a certificate

10 quick questions. Score 70%+ to download your Swift certificate.

Jump to quiz

All Questions on This Page

60 questions
Swift Interview Questions for Freshers
  1. 1. What is Swift and what is it used for?
  2. 2. What is the difference between var and let?
  3. 3. What is an optional and why does Swift use them?
  4. 4. What are the ways to unwrap an optional?
  5. 5. What does the guard statement do and how is it different from if?
  6. 6. What is the difference between a struct and a class?
  7. 7. What are value types and reference types in Swift?
  8. 8. What are the main collection types in Swift?
  9. 9. What is type inference in Swift?
  10. 10. What is optional chaining?
  11. 11. What does the nil-coalescing operator (??) do?
  12. 12. How do you define a function in Swift, and what are argument labels?
  13. 13. What is a closure in Swift?
  14. 14. What are enums in Swift and how are they more than constants?
  15. 15. How do loops and ranges work in Swift?
  16. 16. What is string interpolation in Swift?
  17. 17. What is the difference between a stored and a computed property?
  18. 18. What is a protocol and how do you conform to one?
  19. 19. How does error handling work in Swift?
  20. 20. What do as, as?, and as! do?
  21. 21. Why do struct methods that change properties need the mutating keyword?
  22. 22. What is the difference between Any and AnyObject?
  23. 23. How does the ternary conditional operator work in Swift?
  24. 24. What are tuples in Swift and when would you use one?
  25. 25. How does Swift's switch statement differ from C-style switches?
Swift Intermediate Interview Questions
  1. 26. How does Automatic Reference Counting (ARC) manage memory?
  2. 27. What is a retain cycle and how do you break it?
  3. 28. What is the difference between weak and unowned references?
  4. 29. How do closures capture values, and what is a capture list?
  5. 30. What is an escaping closure?
  6. 31. What are generics and why use them?
  7. 32. What are protocol extensions and default implementations?
  8. 33. What is protocol-oriented programming and how does it differ from class inheritance?
  9. 34. What are associated values in enums?
  10. 35. What do map, filter, and reduce do?
  11. 36. What is a lazy stored property and when do you use one?
  12. 37. What are willSet and didSet property observers?
  13. 38. What are extensions and what can they add?
  14. 39. What is Codable and how do you use it for JSON?
  15. 40. What does the defer statement do?
  16. 41. How do you sort with a custom comparator, and is the sort stable?
  17. 42. What does the some keyword (opaque return type) mean?
  18. 43. What are subscripts in Swift?
  19. 44. What are the access control levels in Swift?
  20. 45. What is the Result type and when do you use it?
Swift Interview Questions for Experienced Developers
  1. 46. How do async/await and structured concurrency work in Swift?
  2. 47. What are actors and what problem do they solve?
  3. 48. What is the Sendable protocol and how does Swift catch data races?
  4. 49. How does copy-on-write work for Swift's value types?
  5. 50. When do you choose a struct over a class for performance, and what are the trade-offs?
  6. 51. What is the difference between a generic constraint and an existential (any Protocol)?
  7. 52. What are property wrappers and how do they work?
  8. 53. How does method dispatch work in Swift (static vs dynamic)?
  9. 54. Why might a method defined only in a protocol extension call the wrong implementation?
  10. 55. What is exclusive access to memory in Swift?
  11. 56. How do typed throws, rethrows, and Result compare for error handling?
  12. 57. How do you define custom and overloaded operators?
  13. 58. How would you migrate a callback-based codebase to async/await?
  14. 59. An iOS app crashes or leaks in production. Walk through your debugging approach.
  15. 60. Design question: how would you implement a thread-safe in-memory cache in Swift?

Swift Interview Questions for Freshers

Freshers25 questions

The fundamentals every entry-level round checks. If any answer here surprises you, that's your study list.

Q1. What is Swift and what is it used for?

Swift is Apple's statically typed, compiled programming language, introduced in 2014 and open-sourced in 2015. It's the primary language for building iOS, macOS, watchOS, and tvOS apps, and it also runs on Linux and Windows for server-side and cross-platform code.

It was designed to be safer and more readable than Objective-C: optionals model missing values, the type system is strict, and ARC handles memory automatically. Beginners get productive quickly while it still powers production apps at scale.

Key point: A one-line definition plus its safety angle beats a memorized feature list. Interviewers open with this to hear how you organize an answer.

Q2. What is the difference between var and let?

let declares a constant: once you assign a value, you can't reassign it. var declares a variable you can reassign. The Swift convention is to reach for let by default and switch to var only when you genuinely need mutation.

For value types, let also makes the whole value immutable, so a let array can't be appended to. For reference types, let fixes the reference but the object it points to can still change.

swift
let pi = 3.14159
// pi = 3.14   // error: cannot assign to a constant

var counter = 0
counter += 1   // fine

let nums = [1, 2, 3]
// nums.append(4)   // error: nums is immutable

Key point: The follow-up is 'why prefer let?'. Answer: it makes intent clear and lets the compiler catch accidental mutation.

Q3. What is an optional and why does Swift use them?

An optional is a type that either holds a value or holds nothing (nil). You write it with a trailing question mark: String? is either a String or nil. Swift uses optionals so the absence of a value is part of the type, not a hidden surprise.

The payoff is safety: the compiler won't let you use an optional as if it always has a value, so a whole class of null crashes disappears. You must unwrap an optional before using what's inside.

swift
var name: String? = "Asha"
name = nil          // allowed, it is optional

var age: Int = 30
// age = nil        // error: Int is not optional

Key point: Optionals are the single most-tested Swift concept. Expect three or four follow-ups chaining off this one answer.

Watch a deeper explanation

Video: What are Optionals in Swift? (donny wals, YouTube)

Q4. What are the ways to unwrap an optional?

The safe ways are optional binding (if let, guard let), the nil-coalescing operator (??) to supply a default, and optional chaining (?.) to keep working through a chain that might be nil. Force unwrapping with ! is the unsafe way: it crashes if the value is nil.

Prefer if let or guard let for control flow, ?? when you have a sensible default, and reserve ! for cases where nil truly can't happen and a crash would be the right outcome.

swift
let input: String? = "42"

if let text = input {
    print("got \(text)")   // runs only when non-nil
}

let safe = input ?? "default"   // nil-coalescing
let length = input?.count       // optional chaining -> Int?

Key point: Reciting all four unwrapping styles and when each fits is exactly what this question screens for.

Q5. What does the guard statement do and how is it different from if?

guard checks a condition and, if it fails, must exit the current scope in its else block (return, throw, break, or continue). If the condition passes, execution continues and any values bound with guard let stay in scope afterward.

The difference from if is the shape it produces: guard handles the failure case up front and keeps the main logic unindented, so you avoid the pyramid of nested if let blocks. It's the standard tool for early exits and precondition checks.

swift
func greet(_ name: String?) {
    guard let name = name, !name.isEmpty else {
        print("no name")
        return
    }
    print("Hello, \(name)")   // name is unwrapped and in scope here
}

Key point: The bound value staying in scope after guard is the detail that shows you actually use it, not just recite it.

Q6. What is the difference between a struct and a class?

A struct is a value type: assigning it or passing it to a function copies it, so changes to the copy don't touch the original. A class is a reference type: assignment shares the same instance, so changes are visible everywhere that reference is held.

Structs get a memberwise initializer for free, don't support inheritance, and suit plain data. Classes support inheritance, identity (===), and deinitializers, and suit shared, mutable state. Apple's guidance is to prefer structs by default.

swift
struct PointS { var x: Int }
class PointC { var x: Int; init(x: Int) { self.x = x } }

var a = PointS(x: 1); var b = a; b.x = 9
print(a.x)   // 1  (copied)

let c = PointC(x: 1); let d = c; d.x = 9
print(c.x)   // 9  (shared reference)
structclass
SemanticsValue (copied)Reference (shared)
InheritanceNoYes
Memberwise initAutomaticManual
Deinit / identity ===NoYes

Key point: This is one of the two or three most-asked Swift questions. Have the copy-versus-share code example ready to draw.

Watch a deeper explanation

Video: Swift Structs - Value Type vs. Reference Type (Sean Allen, YouTube)

Q7. What are value types and reference types in Swift?

Value types (struct, enum, tuple) are copied on assignment and when passed around, so each variable owns an independent copy. Reference types (class, actor, closures) share a single instance, so multiple variables point to the same underlying object.

This drives real behavior: value types are easier to reason about and thread-safer because nobody else can mutate your copy, which is why Swift's standard library types like Array, String, and Dictionary are all structs.

Key point: Array and String are value types.

Q8. What are the main collection types in Swift?

There are three: Array (ordered, allows duplicates), Set (unordered, unique elements, fast membership testing), and Dictionary (key-value pairs with fast lookup by key). All three are value types, so they're copied on assignment, and all three are generic, so an [Int] can't accidentally hold a String and the compiler enforces that.

Pick by need: Array when order matters, Set when you need uniqueness or O(1) contains checks, Dictionary when you look things up by key. Converting an array to a Set is a common way to dedupe or speed up membership tests.

swift
let scores = [90, 85, 90]
let unique = Set(scores)          // {90, 85}
let byName = ["Asha": 90, "Ben": 85]
print(byName["Asha"] ?? 0)        // 90

Q9. What is type inference in Swift?

Type inference lets the compiler work out a variable's type from the value you assign, so let count = 5 is an Int without you writing it. You still get full static typing and its safety; you just skip the boilerplate.

You annotate the type explicitly when the compiler can't infer it, when you want a wider type (let value: Double = 3), or for clarity in an API. Inference is a convenience, not dynamic typing.

swift
let name = "Swift"     // inferred as String
let score = 95         // inferred as Int
let ratio = 0.5        // inferred as Double
let flag: Bool = true  // explicit annotation

Q10. What is optional chaining?

Optional chaining lets you call properties, methods, and subscripts on an optional using ?. If any link in the chain is nil, the whole expression short-circuits and evaluates to nil instead of crashing the way a force unwrap would. Because any step can produce nil, the result of the chain is always itself an optional.

It replaces nested if let checks when you're just reaching through a chain that might not exist, like user?.address?.city, which returns a String? that's nil if user or address is nil.

swift
struct Address { let city: String }
struct User { let address: Address? }

let user: User? = User(address: nil)
let city = user?.address?.city   // String?, here nil
print(city ?? "unknown")         // "unknown"

Q11. What does the nil-coalescing operator (??) do?

The nil-coalescing operator returns the wrapped value of an optional when it's non-nil, and otherwise returns a default you supply on the right-hand side. So writing optional ?? fallback means 'use the value inside the optional, or this default if the optional is nil'. It's the shortest way to turn an optional into a definite value.

It's the cleanest way to turn an optional into a definite value at the point of use, and it short-circuits: the default expression only evaluates when the optional is nil.

swift
let setting: Int? = nil
let timeout = setting ?? 30   // 30

let name: String? = "Asha"
print(name ?? "guest")        // "Asha"

Q12. How do you define a function in Swift, and what are argument labels?

A function uses func, a name, parameters with types, and an optional return type after ->. Each parameter has an argument label (used at the call site) and a parameter name (used inside the body); they default to the same word.

Argument labels make calls read like sentences: move(from: a, to: b). Use _ to drop a label when it adds no clarity. This label system is why Swift call sites read more clearly than most languages.

swift
func greet(person name: String, from city: String) -> String {
    return "Hello \(name) from \(city)"
}
greet(person: "Asha", from: "Pune")

func double(_ n: Int) -> Int { n * 2 }   // no label
double(21)   // 42

Q13. What is a closure in Swift?

A closure is a self-contained block of code you can pass around and call later, like an anonymous function. It can capture and store references to variables from the surrounding scope, which is where the name comes from.

Closures power callbacks, completion handlers, and functional methods like map and sorted. Swift has trailing-closure syntax and shorthand argument names ($0, $1) that make short closures compact.

swift
let nums = [3, 1, 2]
let sorted = nums.sorted { $0 < $1 }   // trailing closure, shorthand args
print(sorted)   // [1, 2, 3]

let greet = { (name: String) in print("Hi \(name)") }
greet("Asha")

Key point: Being able to write a trailing closure with $0 shorthand from memory is the fresher-level bar here.

Watch a deeper explanation

Video: Introduction to Swift: Closures (Paul Hudson, YouTube)

Q14. What are enums in Swift and how are they more than constants?

An enum defines a type with a fixed set of cases, like Direction.north. Unlike C-style enums, Swift enums are first-class types: they can have methods, computed properties, conform to protocols, and carry associated values so each case can store its own data.

That makes enums a modeling tool. A Result can be .success(value) or .failure(error), each carrying different data, and a switch over an enum is exhaustive, so the compiler flags a missing case.

swift
enum Payment {
    case cash
    case card(last4: String)
}

let p = Payment.card(last4: "1234")
switch p {
case .cash: print("cash")
case .card(let last4): print("card ending \(last4)")
}

Q15. How do loops and ranges work in Swift?

The for-in loop iterates over any sequence: arrays, ranges, dictionaries, and strings. Ranges come in two forms: 1...5 is a closed range that includes 5, and 1..<5 is a half-open range that excludes 5. For condition-driven loops there's while, which checks before each pass, and repeat-while, which runs the body once before checking.

You can iterate with indices via enumerated() to get (index, element) pairs, and stride(from:to:by:) handles custom steps. There's no C-style for loop; ranges and sequences replace it.

swift
for i in 1...3 { print(i) }        // 1 2 3
for i in 0..<3 { print(i) }        // 0 1 2

let names = ["Asha", "Ben"]
for (i, name) in names.enumerated() {
    print("\(i): \(name)")
}

Q16. What is string interpolation in Swift?

String interpolation builds a string by embedding expressions directly inside a literal with the \(...) syntax. Anything you put inside the parentheses is evaluated and its result is inserted into the string, so you avoid clumsy manual concatenation with the plus operator. It reads cleanly and works with any expression, not just plain variables.

It works with any expression, not just variables, and Swift strings are Unicode-correct value types. For multi-line text, triple-quoted strings ("""...""") preserve line breaks.

swift
let name = "Asha"
let score = 95
print("\(name) scored \(score), avg \(Double(score) / 2)")
// Asha scored 95, avg 47.5

Q17. What is the difference between a stored and a computed property?

A stored property holds an actual value in memory as part of the instance. A computed property has no storage of its own; instead it runs a getter, and optionally a setter, every single time you access it, deriving its value from other properties on the fly. So a stored property remembers a value while a computed one recalculates it each time.

Use computed properties for values that should always stay in sync with their inputs, like an area derived from width and height, so there's no stale copy to keep updated.

swift
struct Rectangle {
    var width: Double
    var height: Double
    var area: Double { width * height }   // computed, read-only
}
var r = Rectangle(width: 3, height: 4)
print(r.area)   // 12
r.width = 5
print(r.area)   // 20, always current

Q18. What is a protocol and how do you conform to one?

A protocol declares a set of requirements, methods, properties, or initializers, without implementing any of them. A type conforms by naming the protocol in its declaration and then providing everything the protocol requires. It's Swift's version of an interface, and one type can conform to many protocols at once, which is central to how Swift models shared behavior.

Protocols let unrelated types share a capability: anything conforming to Comparable can be sorted, anything conforming to your Drawable can be drawn. Protocol extensions can add default implementations, so conformers get behavior for free.

swift
protocol Greetable {
    var name: String { get }
    func greet() -> String
}

struct Person: Greetable {
    let name: String
    func greet() -> String { "Hi, I am \(name)" }
}

Q19. How does error handling work in Swift?

A function that can fail is marked throws and uses throw to raise an error (any type conforming to the Error protocol). Callers must handle it with do/catch, or convert it with try? (turns a failure into nil) or try! (crashes on failure).

Errors are usually enums that group related failures. The try keyword marks every call that can throw, so failure points are visible in the code rather than hidden.

swift
enum LoginError: Error { case wrongPassword }

func login(_ pw: String) throws {
    if pw != "secret" { throw LoginError.wrongPassword }
}

do {
    try login("nope")
} catch {
    print("failed: \(error)")
}

Q20. What do as, as?, and as! do?

as is a guaranteed cast the compiler already knows is safe (like a subclass to a superclass, or a literal to a wider type). as? is a conditional downcast that returns an optional, nil if the cast fails. as! force-casts and crashes if it fails.

The safe pattern is as? with if let or guard let, common when pulling a specific type out of an [Any] or checking a superclass reference against a subclass.

swift
let items: [Any] = [1, "two", 3.0]
for item in items {
    if let s = item as? String {
        print("string: \(s)")
    }
}

Q21. Why do struct methods that change properties need the mutating keyword?

Structs are value types, and by default their methods can't modify their own stored properties. Marking a method mutating tells Swift the method changes the value, so it's only callable on a var instance, not a let one.

This makes mutation explicit and is why calling a mutating method on a constant struct is a compile error. Classes don't need it because they're reference types; changing a property doesn't change the reference.

swift
struct Counter {
    var count = 0
    mutating func bump() { count += 1 }
}
var c = Counter()
c.bump()   // count is now 1

let fixed = Counter()
// fixed.bump()   // error: cannot mutate a let struct

Q22. What is the difference between Any and AnyObject?

Any can hold an instance of absolutely any type at all: value types like Int and String, reference types, functions, and even optionals. AnyObject is narrower; it can hold only instances of class types, meaning reference types. So every AnyObject is an Any, but the reverse isn't true, and structs can't be stored in an AnyObject.

Reach for these sparingly. They erase type information, so you lose the compiler's help and usually have to cast back with as? before you can do anything useful. Generics are almost always the better tool when you want to stay flexible but keep type safety.

Q23. How does the ternary conditional operator work in Swift?

The ternary operator condition ? a : b evaluates the condition and returns a when true, b when false. It's a compact expression for a simple two-way choice, useful where an if statement would be too heavy, like inline in a return or an assignment.

Keep it for genuinely simple choices. Nesting ternaries hurts readability fast, and an if/else or a switch reads better once there's real logic in the branches.

swift
let score = 72
let grade = score >= 60 ? "pass" : "fail"
print(grade)   // "pass"

Q24. What are tuples in Swift and when would you use one?

A tuple groups several values into one compound value, and each element can be a different type: (Int, String) or a named form like (code: Int, message: String). Tuples are value types, so they're copied on assignment, and you can pull elements out by index or by name.

Use them for small, temporary groupings, like returning two values from a function, without defining a whole struct. Once a grouping is meaningful enough to name or reuse, promote it to a struct.

swift
func minMax(_ nums: [Int]) -> (min: Int, max: Int) {
    return (nums.min()!, nums.max()!)
}
let result = minMax([3, 1, 9, 4])
print(result.min, result.max)   // 1 9

Key point: The follow-up is 'tuple or struct?'. Answer: tuple for a quick local grouping, struct once it needs a name, methods, or reuse.

Q25. How does Swift's switch statement differ from C-style switches?

A Swift switch must be exhaustive: it has to cover every possible value or include a default, so the compiler flags a missing case at build time. There's no implicit fall-through between cases, so you don't need break, and one case can match multiple values or ranges.

It's also a full pattern-matching tool: cases can match ranges (0..<10), tuples, enum cases with associated values, and add where clauses for extra conditions. That makes switch far more expressive than a plain integer dispatch.

swift
let point = (2, 0)
switch point {
case (0, 0): print("origin")
case (let x, 0): print("on x-axis at \(x)")
case (0, let y): print("on y-axis at \(y)")
case (let x, let y) where x == y: print("diagonal")
default: print("elsewhere")
}
Back to question list

Swift Intermediate Interview Questions

Intermediate20 questions

For candidates with working experience: language mechanics, memory, protocols, and the questions that separate users from understanders.

Q26. How does Automatic Reference Counting (ARC) manage memory?

ARC tracks how many strong references currently point to each class instance. The compiler inserts the retain and release calls for you at compile time, and the moment an instance's reference count drops to zero, it's deallocated immediately and its deinit method runs. There's no background thread scanning for garbage; it all happens deterministically.

ARC applies only to reference types (classes), not value types, which are copied instead. It's not garbage collection: there's no background sweep and no pause, which is deterministic but means reference cycles won't get cleaned up on their own.

swift
class File {
    let name: String
    init(name: String) { self.name = name }
    deinit { print("\(name) freed") }
}

var a: File? = File(name: "log")
a = nil   // count hits 0, prints "log freed"

Key point: The near-guaranteed follow-up is 'so what happens with a reference cycle?'. Have the weak/unowned answer loaded and ready.

Watch a deeper explanation

Video: Swift - Retain Cycle, Automatic Reference Counting, Memory Leak - iOS Interview Questions (Sean Allen, YouTube)

Q27. What is a retain cycle and how do you break it?

A retain cycle happens when two objects hold strong references to each other, so neither's count ever reaches zero and both leak. The classic case is a parent holding a child and the child holding the parent back, or a closure capturing self while self holds the closure.

You break it by making one side of the relationship weak or unowned so it doesn't contribute to the reference count. Delegates are usually weak, and closures use a capture list like [weak self] to avoid holding their owner alive.

swift
class ViewModel {
    var onDone: (() -> Void)?
    func load() {
        onDone = { [weak self] in
            self?.finish()   // no cycle: closure does not retain self
        }
    }
    func finish() {}
}

Key point: Naming the two common cycles (delegate, closure capturing self) and their fixes is exactly what a mid-level round wants.

Q28. What is the difference between weak and unowned references?

Both avoid increasing the reference count, so both break retain cycles. weak references are always optional and become nil automatically when the object is deallocated, so accessing them safely is fine. unowned references are non-optional and assume the object outlives the reference; accessing one after the object is gone crashes.

The rule of thumb: use weak when the other object can legitimately disappear first (a delegate), and unowned when the reference's lifetime is guaranteed to be inside the other object's lifetime (a child that never outlives its parent).

weakunowned
OptionalYes, becomes nilNo, non-optional
After deallocationindicates nil, safeCrash if accessed
Use whenOther object may vanish firstOther object outlives this reference

Key point: The trap is defaulting to unowned for brevity. Choosing weak unless the lifetime is truly guaranteed is the safer, more production-ready answer.

Q29. How do closures capture values, and what is a capture list?

By default a closure captures variables from its surrounding scope by reference, so it sees later changes to them and keeps any captured objects alive for as long as the closure exists. That's convenient, but it can create a retain cycle when a stored closure captures self and the same object also holds the closure, so neither can be freed.

A capture list, written in brackets before the parameters, lets you control capture: [weak self] captures self weakly, [unowned self] captures it unowned, and [value = someValue] captures a copy of the current value rather than a live reference.

swift
var total = 0
let add = { total += 1 }   // captures total by reference
add(); add()
print(total)   // 2

var x = 10
let snapshot = { [x] in print(x) }   // captures a copy
x = 99
snapshot()   // 10, not 99

Watch a deeper explanation

Video: Swift Closures Explained (Sean Allen, YouTube)

Q30. What is an escaping closure?

An escaping closure is one that outlives the function it's passed to: it's stored for later or called after the function returns, like a completion handler that runs when a network call finishes. You mark it @escaping in the parameter type.

Non-escaping is the default because it lets the compiler optimize and skip retain-cycle worries. Escaping closures can capture self and create cycles, which is why you often see [weak self] inside them.

swift
var handlers: [() -> Void] = []

func store(_ handler: @escaping () -> Void) {
    handlers.append(handler)   // escapes: stored for later
}

func runNow(_ action: () -> Void) {
    action()   // non-escaping: called before return
}

Q31. What are generics and why use them?

Generics let you write code that works with any type while keeping full type safety. A generic function or type uses a placeholder (like T) that the caller fills in, so one implementation serves Int, String, or your own types without duplication or casting.

The whole standard library is generic: Array<Element>, Dictionary<Key, Value>, Optional<Wrapped>. You can constrain a generic with where clauses or protocol bounds (T: Comparable) so it only accepts types that support the operations you need.

swift
func firstIndex<T: Equatable>(of value: T, in array: [T]) -> Int? {
    for (i, item) in array.enumerated() where item == value {
        return i
    }
    return nil
}
firstIndex(of: "b", in: ["a", "b", "c"])   // 1

Key point: Adding a protocol constraint (T: Equatable) in practice shows you understand generics as more than a wildcard.

Q32. What are protocol extensions and default implementations?

A protocol extension adds methods and computed properties to a protocol itself, rather than to one concrete type. Any type that conforms to the protocol gets those implementations for free, so you write shared default behavior once in a single place instead of copying it into every conforming type. Conformers can still override a default where they need to differ.

This is the heart of protocol-oriented programming: define capability in a protocol, ship sensible defaults in its extension, and let types override only where they differ. It favors composition over class inheritance.

swift
protocol Named { var name: String { get } }

extension Named {
    func label() -> String { "Name: \(name)" }   // default for all conformers
}

struct City: Named { let name: String }
City(name: "Pune").label()   // "Name: Pune"

Q33. What is protocol-oriented programming and how does it differ from class inheritance?

Protocol-oriented programming models behavior with protocols and protocol extensions instead of a class hierarchy. A type composes several protocols, each one contributing a capability along with default implementations from its extension, rather than inheriting everything from a single superclass. You build up behavior by adopting small focused protocols instead of extending one deep base class.

The advantages: value types (structs) can participate, a type can adopt many protocols where it can only have one superclass, and you avoid deep, brittle inheritance chains. Apple has pushed this style since the 'Protocol-Oriented Programming in Swift' talk.

Key point: Saying 'structs can't inherit but can conform to many protocols' cleanly explains why this style exists in Swift.

Q34. What are associated values in enums?

Associated values let each enum case store its own related data of any type. So a case isn't just a bare label; it can carry a payload attached to it, and different cases in the same enum can carry completely different payloads. That turns an enum into a way to model a value that's exactly one of several shapes, each shape holding the data that fits it.

This makes enums a modeling tool for states that come with data: a Result is .success(T) or .failure(Error), a network response is .loading, .loaded(data), or .failed(message). You extract the values by pattern matching in a switch or if case.

swift
enum Barcode {
    case upc(Int, Int, Int, Int)
    case qr(String)
}

let code = Barcode.qr("HELLO")
switch code {
case .upc(let a, let b, let c, let d): print(a, b, c, d)
case .qr(let text): print(text)   // "HELLO"
}

Q35. What do map, filter, and reduce do?

map transforms every element and returns a new collection of results. filter keeps only elements that pass a test. reduce combines all elements into a single value using a running accumulator. All three take a closure and don't mutate the original.

compactMap is the common fourth: it maps and drops any nil results in one pass, handy when your transform returns optionals. flatMap flattens nested collections.

swift
let nums = [1, 2, 3, 4]
let doubled = nums.map { $0 * 2 }        // [2, 4, 6, 8]
let evens = nums.filter { $0 % 2 == 0 }  // [2, 4]
let sum = nums.reduce(0, +)              // 10

["1", "x", "3"].compactMap { Int($0) }   // [1, 3]

Q36. What is a lazy stored property and when do you use one?

A lazy property isn't computed until the first time it's accessed. You mark it lazy var, and its initializer runs once, on first use, then the result is stored. It must be var because it's set after the instance is created.

Use it when the initial value is expensive to build or depends on other properties that must exist first, and you might not always need it. The cost is that lazy properties aren't thread-safe on first access.

swift
class Report {
    lazy var data: [Int] = {
        print("building")   // runs only on first access
        return Array(1...1000)
    }()
}
let r = Report()   // nothing printed yet
r.data             // "building" prints now

Q37. What are willSet and didSet property observers?

Property observers run code when a stored property's value changes. willSet fires just before the new value is stored, giving you newValue, and didSet fires right after the change, giving you oldValue so you can compare. Neither observer runs during initialization, only on later assignments, which is a detail people forget.

Use them for side effects tied to a value change: updating a UI label when a model property changes, validating a bound, or logging. They keep that reactive logic next to the property instead of scattered across setters.

swift
class Thermostat {
    var temperature: Int = 20 {
        didSet {
            print("changed from \(oldValue) to \(temperature)")
        }
    }
}
Thermostat().temperature = 25   // changed from 20 to 25

Q38. What are extensions and what can they add?

An extension adds functionality to an existing type, even one you don't own like Int or String from the standard library. Extensions can add computed properties, methods, initializers, subscripts, and protocol conformance to that type. What they can't do is add stored properties or override an existing method, since they only extend rather than replace.

They're a clean way to organize code (group a type's protocol conformance in its own extension) and to add small helpers to standard types without subclassing. Conforming a type to a protocol in an extension is a very common pattern.

swift
extension Int {
    var isEven: Bool { self % 2 == 0 }
    func times(_ action: () -> Void) {
        for _ in 0..<self { action() }
    }
}
4.isEven       // true
3.times { print("hi") }

Q39. What is Codable and how do you use it for JSON?

Codable is a type alias for Encodable and Decodable. Marking a type Codable lets Swift automatically convert it to and from formats like JSON, as long as all its properties are also Codable. JSONEncoder and JSONDecoder do the work.

When JSON keys don't match your property names, a CodingKeys enum maps them. For custom logic you can implement init(from:) and encode(to:) yourself, but the automatic synthesis covers most models.

swift
struct User: Codable {
    let id: Int
    let name: String
}

let json = #"{"id": 1, "name": "Asha"}"#.data(using: .utf8)!
let user = try JSONDecoder().decode(User.self, from: json)
print(user.name)   // "Asha"

Q40. What does the defer statement do?

defer schedules a block of code to run when the current scope exits, no matter how it exits, whether by returning normally, throwing an error, or breaking out of a loop. If you write several defer blocks in one scope, they run in reverse order of declaration, so the last one you wrote runs first. The block always runs, which is the point.

It's the tool for cleanup that must always happen: closing a file, releasing a lock, undoing a temporary change. Putting the cleanup right next to the setup keeps the two together even when there are multiple exit points.

swift
func process() {
    print("open")
    defer { print("close") }   // runs last, on any exit
    print("work")
}
process()   // open, work, close

Q41. How do you sort with a custom comparator, and is the sort stable?

sorted(by:) returns a new sorted array and sort(by:) sorts in place, both taking a closure that returns true when the first argument should come before the second. For types that are Comparable, plain sorted() works with no closure.

As of Swift 5, the standard library sort is guaranteed stable: elements that compare equal keep their original relative order. That lets you sort by a secondary key, then a primary key, and get a correct multi-level ordering.

swift
var people = [("Ben", 30), ("Asha", 30), ("Cara", 25)]
people.sort { $0.1 < $1.1 }   // by age; equal ages keep prior order
print(people)
// [("Cara", 25), ("Ben", 30), ("Asha", 30)]

Q42. What does the some keyword (opaque return type) mean?

some Protocol is an opaque return type: it says 'this returns one specific concrete type that conforms to Protocol, and it's the same type every time, but I'm not telling the caller which one'. The caller knows the capabilities, not the exact type.

SwiftUI uses it everywhere (some View) because view bodies return one complex concrete type you don't want to spell out. It differs from returning the protocol itself, which allows different types and loses some compiler optimizations.

swift
func makeAdder() -> some Sequence {
    return [1, 2, 3]   // concrete type Array<Int>, hidden behind some Sequence
}

Q43. What are subscripts in Swift?

A subscript lets you access elements of a type with bracket syntax, like array[0] or dictionary[key]. You define one with the subscript keyword, giving it parameters and a return type, plus an optional setter so the bracket access can be assigned to. It's how a type exposes collection-style access instead of forcing callers to call a named method.

You add subscripts to your own types to give them collection-like access, a matrix indexed by row and column, or a wrapper that exposes its contents by key. They can be overloaded to take different parameter types.

swift
struct Grid {
    var data = Array(repeating: 0, count: 9)
    subscript(row: Int, col: Int) -> Int {
        get { data[row * 3 + col] }
        set { data[row * 3 + col] = newValue }
    }
}
var g = Grid()
g[1, 1] = 5
print(g[1, 1])   // 5

Q44. What are the access control levels in Swift?

From most to least restrictive: private (the enclosing declaration and its extensions in the same file), fileprivate (the whole file), internal (the whole module, the default), public (other modules can use it but not subclass or override), and open (other modules can also subclass and override).

The distinction between public and open matters for library authors: public exposes an API for use, open additionally allows external subclassing. Default internal means you only annotate when you want to widen or narrow visibility.

Q45. What is the Result type and when do you use it?

Result<Success, Failure> is a standard-library enum with two cases, .success(value) and .failure(error), where the error type conforms to Error. It packages the outcome of an operation that can fail into a single value you can store, pass, and inspect later.

It shines in asynchronous callbacks, where you can't use throw across a closure boundary: a completion handler takes a Result and the caller switches over it. You can also convert it back to throwing code with try result.get().

swift
func fetch(completion: (Result<Int, Error>) -> Void) {
    completion(.success(42))
}
fetch { result in
    switch result {
    case .success(let value): print(value)
    case .failure(let error): print(error)
    }
}
Back to question list

Swift Interview Questions for Experienced Developers

Experienced15 questions

advanced rounds probe internals, concurrency, design judgment, and production scars. Expect every answer here to draw a follow-up.

Q46. How do async/await and structured concurrency work in Swift?

async marks a function that can suspend; await marks the point where it might. When an async function awaits, it gives up its thread so other work can run, then resumes when the awaited operation completes. This replaces nested completion handlers with straight-line code.

Structured concurrency ties child tasks to a parent scope: async let and TaskGroup spawn concurrent work whose lifetime is bounded by the enclosing function, so cancellation and errors propagate predictably and nothing leaks past the scope.

swift
func loadProfile() async throws -> Profile {
    async let user = fetchUser()      // starts concurrently
    async let posts = fetchPosts()
    return try await Profile(user: user, posts: posts)
}

Key point: The follow-up is usually 'how is this different from GCD?'. Answer: it's cooperative, structured, and the compiler enforces sendability.

Q47. What are actors and what problem do they solve?

An actor is a reference type that protects its mutable state from data races. Only one task can access an actor's isolated state at a time; the compiler routes external access through await and serializes it, so you can't accidentally read and write shared state from two threads at once.

Actors replace manual locks and serial queues for state isolation. The main-thread version is the @MainActor annotation, which guarantees UI-touching code runs on the main thread. The trade-off is that cross-actor calls are async.

swift
actor Counter {
    private var value = 0
    func increment() { value += 1 }
    func current() -> Int { value }
}

let c = Counter()
await c.increment()
print(await c.current())

Key point: Naming reentrancy (an actor can suspend mid-method and let other calls in) is the detail that marks real concurrency experience.

Q48. What is the Sendable protocol and how does Swift catch data races?

Sendable marks a type as safe to pass across concurrency boundaries (between tasks or actors). Value types with Sendable members are Sendable automatically; classes must be immutable or internally synchronized to qualify. The compiler checks Sendable conformance to flag potential data races at compile time.

This is Swift's strict concurrency checking: when you send a non-Sendable value into a different isolation domain, the compiler warns or errors, turning a class of runtime races into build failures. @unchecked Sendable is the escape hatch when you've synchronized manually.

Q49. How does copy-on-write work for Swift's value types?

Swift's standard collections (Array, Dictionary, Set, String) are value types, but copying one doesn't duplicate its storage immediately. The copy shares the same underlying buffer until one side mutates it; only then is a real copy made. That keeps value semantics cheap.

The check uses isKnownUniquelyReferenced on the internal storage: if the buffer has one owner, mutate in place; if it's shared, copy first. You can implement the same pattern for your own value types that wrap a class-backed buffer.

swift
var a = [1, 2, 3]
var b = a        // shares storage, no copy yet
b.append(4)      // b is now unique-ish, storage copied here
print(a)         // [1, 2, 3], unchanged

Key point: Explaining that the copy is deferred until mutation, not skipped, is what separates a real answer from 'arrays are copied'.

Q50. When do you choose a struct over a class for performance, and what are the trade-offs?

Structs live on the stack when possible and avoid ARC's retain/release traffic, so small, frequently passed value types are often faster and easier to reason about. Apple's guidance is to default to structs and reach for classes only when you need identity, inheritance, or shared mutable state.

The trade-offs cut both ways: large structs are expensive to copy if copy-on-write doesn't apply, and structs holding class references still incur ARC on those references. Deeply nested value types can also stress the compiler and runtime, so measure rather than assume.

Relative cost of common per-access operations

Approximate order-of-magnitude tiers, not measured benchmarks. Use to reason about where cost concentrates, not for exact numbers.

Struct field access (stack)
1 relative cost tier
Class property access + ARC
10 relative cost tier
Cross-actor await call
1,000 relative cost tier
  • Struct field access (stack): No reference counting, cache-friendly
  • Class property access + ARC: Retain/release traffic on the reference
  • Cross-actor await call: Suspension and scheduling overhead

Q51. What is the difference between a generic constraint and an existential (any Protocol)?

A generic constraint (func f<T: Shape>(_ x: T)) resolves to one concrete type at each call site, so the compiler can specialize and inline. An existential (any Shape) is a boxed value that can hold any conforming type, decided at runtime, which costs indirection and dynamic dispatch.

Swift now makes existentials explicit with the any keyword precisely because they're not free. Prefer generics or opaque types (some) when the concrete type is known at compile time; use any only when you genuinely need to store a heterogeneous mix behind one protocol.

Key point: Knowing why any was made explicit (to surface the runtime cost) is a strong signal of keeping up with the language.

Q52. What are property wrappers and how do they work?

A property wrapper is a type that adds behavior to a property by wrapping its storage. You mark it @propertyWrapper and give it a wrappedValue; then any property annotated with your wrapper's name routes get and set through it. The compiler generates the backing storage.

They factor out repeated property logic: clamping to a range, thread-safe access, persistence to UserDefaults. SwiftUI's @State, @Published, and @Environment are all property wrappers, which is why they behave differently from plain properties.

swift
@propertyWrapper
struct Clamped {
    private var value: Int
    private let range: ClosedRange<Int>
    init(wrappedValue: Int, _ range: ClosedRange<Int>) {
        self.range = range
        self.value = min(max(wrappedValue, range.lowerBound), range.upperBound)
    }
    var wrappedValue: Int {
        get { value }
        set { value = min(max(newValue, range.lowerBound), range.upperBound) }
    }
}

struct Player { @Clamped(0...100) var health = 120 }
Player().health   // 100

Q53. How does method dispatch work in Swift (static vs dynamic)?

Swift uses several dispatch mechanisms. Value types and final methods use static dispatch, resolved at compile time and inlinable. Non-final class methods use dynamic dispatch through a vtable. Protocol requirements use a witness table. Methods marked @objc or dynamic use Objective-C message dispatch, the slowest but most flexible.

It matters for performance and behavior: marking a class or method final enables static dispatch and inlining, while protocol-extension methods that aren't protocol requirements dispatch statically, which produces a well-known gotcha where the wrong implementation runs.

Key point: The protocol-extension dispatch gotcha (a non-requirement method calls the extension's version, not the type's override) is a favorite senior follow-up.

Q54. Why might a method defined only in a protocol extension call the wrong implementation?

If a method is declared in the protocol itself, calls go through the witness table and dynamic dispatch picks the conforming type's version. If a method exists only in the protocol extension (not listed as a protocol requirement), it's dispatched statically based on the variable's declared type, so a type's own implementation can be bypassed.

The fix is to add the method to the protocol's requirement list, which forces dynamic dispatch. This trips people up when they store an object as its protocol type and see the extension's default run instead of the concrete override.

swift
protocol Greeter { func hi() }        // requirement -> dynamic
extension Greeter {
    func hi() { print("default") }
    func bye() { print("ext bye") }     // not a requirement -> static
}
struct En: Greeter {
    func hi() { print("hello") }
    func bye() { print("goodbye") }
}
let g: Greeter = En()
g.hi()    // "hello"  (dynamic)
g.bye()   // "ext bye" (static, the gotcha)

Q55. What is exclusive access to memory in Swift?

Swift enforces that a variable can't be accessed in conflicting ways at the same time: you can't have two simultaneous writes, or a write overlapping a read, to the same storage. The compiler checks most of this statically, and the runtime catches the rest, trapping on a violation.

It surfaces with inout parameters (passing the same variable to two inout slots is an error) and with mutating methods that call back into the same value. The rule is what makes value semantics and safe optimization possible.

swift
func swapInts(_ a: inout Int, _ b: inout Int) {
    let t = a; a = b; b = t
}
var x = 1
// swapInts(&x, &x)   // error: overlapping access to x

Q56. How do typed throws, rethrows, and Result compare for error handling?

Plain throws propagates any Error; the caller doesn't know the concrete type without inspecting it. rethrows applies to functions that only throw because a closure argument throws, so they're non-throwing when passed a non-throwing closure. Typed throws (throws(MyError)) constrains a function to one error type, useful in constrained or embedded contexts.

Result reifies the outcome into a value, which suits async callbacks and storing outcomes, while throws suits synchronous straight-line code. Modern async code mostly uses async throws and lets structured concurrency propagate errors.

Q57. How do you define custom and overloaded operators?

You overload an existing operator by writing a static func with the operator as its name inside a type, or a free function. For a brand-new operator symbol you first declare it with operator plus its fixity (prefix, infix, postfix) and, for infix, a precedence group.

Use overloading for types where an operator reads naturally (adding two vectors with +). Be sparing with entirely new operators: unfamiliar symbols hurt readability, and most teams prefer named methods unless the domain strongly implies an operator.

swift
struct Vector { var x, y: Double }

extension Vector {
    static func + (l: Vector, r: Vector) -> Vector {
        Vector(x: l.x + r.x, y: l.y + r.y)
    }
}
Vector(x: 1, y: 2) + Vector(x: 3, y: 4)   // (4, 6)

Q58. How would you migrate a callback-based codebase to async/await?

Wrap the leaf callbacks first with continuations: withCheckedContinuation for non-throwing APIs and withCheckedThrowingContinuation for throwing ones, resuming the continuation exactly once inside the old completion handler. That gives you async entry points without rewriting the call chain at once.

Then work outward, replacing callback signatures with async throws and folding nested handlers into straight-line awaits. Adopt strict concurrency checking gradually per module, fix Sendable warnings at the boundaries, and keep UI updates on @MainActor.

swift
func fetchAsync() async throws -> Data {
    try await withCheckedThrowingContinuation { cont in
        oldFetch { data, error in
            if let error { cont.resume(throwing: error) }
            else { cont.resume(returning: data!) }
        }
    }
}

Key point: Naming 'resume exactly once' matters: resuming a continuation twice or never is a classic bug this question probes.

Q59. An iOS app crashes or leaks in production. Walk through your debugging approach.

Start from the signal: symbolicate the crash reports (Xcode Organizer, or a service like Crashlytics) to find the faulting frame, and check whether it correlates with a recent release or device/OS. For force-unwrap and array-bounds crashes, the stack usually points straight at the line.

For leaks and cycles, use Instruments: the Leaks and Allocations instruments to watch growth, and the memory graph debugger to find retain cycles (often a closure capturing self or a strong delegate). Then fix at the right layer, weak capture, break the cycle, add a bound, and confirm with the same tool.

Key point: The structure is: observe, isolate, fix, confirm.

Q60. Design question: how would you implement a thread-safe in-memory cache in Swift?

Clarify first: eviction policy (LRU, size or count bound), concurrency needs, and whether entries expire. Then pick isolation: an actor is the modern choice, since it serializes access to the backing dictionary without manual locks and composes with async callers. For a synchronous API, a serial DispatchQueue or an NSLock around the dictionary works.

For eviction, back an LRU with a dictionary plus a doubly linked list so both lookup and recency updates stay O(1). The operational edges: memory pressure (respond to didReceiveMemoryWarning or use NSCache which evicts under pressure automatically), and thundering-herd on cache misses matters.

swift
actor Cache<Key: Hashable, Value> {
    private var store: [Key: Value] = [:]
    func get(_ key: Key) -> Value? { store[key] }
    func set(_ key: Key, _ value: Value) { store[key] = value }
}

let cache = Cache<String, Int>()
await cache.set("a", 1)
print(await cache.get("a") ?? -1)   // 1
Back to question list

Swift vs Objective-C, Kotlin, and Java

Swift wins when you want type safety and modern syntax on Apple platforms without the ceremony of Objective-C. It trades Objective-C's dynamic runtime and mature tooling history for a stricter compiler that catches nil and type mistakes early. Against Kotlin, the story is close: both are modern, null-safe, statically typed languages, but Swift targets Apple's ecosystem first while Kotlin targets Android and the JVM. Java sits further away, older and more verbose, with null handled by convention rather than the type system. Saying these trade-offs out loud is itself an interview signal: it shows you pick tools on merits, not habit.

LanguageNull handlingBest atWatch out for
SwiftOptionals in the type systemiOS, macOS, Apple platforms, server-sideSmaller ecosystem off Apple platforms
Objective-Cnil messaging, no compiler checkLegacy Apple codebases, C interopVerbose syntax, weaker safety
KotlinNullable types in the type systemAndroid, JVM, multiplatformApple support via KMP is still maturing
JavaNull by convention, no checkEnterprise backends, Android (older)Verbose, null pointer exceptions

How to Prepare for a Swift Interview

Prepare in layers, and practice out loud. Most Swift rounds move from concept questions to live coding to a design or debugging discussion, so rehearse each stage rather than only reading answers.

  • Master your tier's concepts until you can explain them without notes, then read one tier up for the stretch questions.
  • Type and run every snippet in a playground; modifying working code cements it far faster than reading.
  • Practice thinking aloud on small problems with a timer, because your process, not just the final answer is the technical point.
  • Take the quiz on this page to find weak spots, then revisit those questions before the real round.

The typical Swift interview flow

1Recruiter or phone screen
background, motivation, a few concept checks
2Technical concepts
optionals, value vs reference types, ARC, protocols
3Live coding
write and explain a function or small feature under observation
4Design or debugging
trade-offs, reading unfamiliar code, follow-ups

Earlier rounds increasingly run as AI coding interviews. The concepts on this page are what gets probed at every stage.

Test Yourself: Swift Quiz

Ready to test your Swift knowledge?

10 questions, about 6 minutes. Score 70% or higher to earn a shareable certificate.

10 questions Instant feedback Free certificate on 70%+

Frequently  Asked  Questions

Which Swift topics should I prioritize before a technical round?

Prioritize the areas that change real decisions: fundamentals, practical tasks, debugging, trade-offs, and evidence. The highest-value questions are tied to a project example, a validation check, or a failure mode.

Are these questions enough to pass a Swift interview?

They cover the question-answer portion well, but most Swift rounds also include live coding: writing a function or a small feature under observation while explaining your thinking. solving small problems out loud with a timer is the practice path. Our AI coding interview prep guide covers that format, including how automated evaluation reads your process.

Do I need SwiftUI and UIKit knowledge for a Swift interview?

For an iOS role, yes, expect UI framework questions alongside language questions. This page focuses on the Swift language itself, which is what every Swift interview tests first. Layer framework prep on top based on the job description: SwiftUI for newer teams, UIKit for teams with existing apps, often both.

How long does it take to prepare for a Swift interview?

If you write Swift at work or in study, one to two weeks of an hour a day covers this bank with practice runs. Starting colder, plan three to four weeks and write code daily in a playground; reading answers without typing them is how preparation quietly fails.

Do interviewers ask exactly these questions?

The concepts repeat far more reliably than the exact wording. Interviewers rephrase, chain follow-ups, and push toward whatever you claim confidence in. Prepare the underlying ideas, optionals, value versus reference semantics, ARC, and protocols, and the phrasing takes care of itself.

Is there a way to test my Swift knowledge?

Yes. The quiz on this page scores you as you go and explains every answer, so you find your weak spots fast. Pass the threshold and you can download a shareable certificate with your name and score, free and with no sign-up. It's the quickest way to check yourself before the real round.

From the team that builds coding interviews

Hyring builds the AI Coding Interviewer that runs live technical rounds in 20+ languages for 5,000+ hiring teams. These Swift questions and scoring notes reflect what actually gets asked and evaluated inside the interviews we host.

See how the AI Coding Interviewer works

Sources

Adithyan RKWritten by Adithyan RK
Surya N
Fact-checked by Surya N
Published on: 29 Apr 2026Last updated: 12 Jul 2026
Share: