Feb 17, 2025 iOS

iOS interview question on struct and Classes

Here are some commonly asked questions and answers on Struct vs. Class in Swift:

Table of Contents

1. What is the primary difference between struct and class in Swift?

Answer:
The main difference is that structs are value types, whereas classes are reference types. This means:

  • Structs are copied when assigned to a new variable or passed to a function.
  • Classes share the same reference, meaning changes made to one instance affect all references to that instance.

2. When should you use a struct instead of a class?

Answer:
Use structs when:

  • You need value-type behavior (independent copies).
  • Your data is relatively simple and doesn’t require shared state or identity.
  • You want thread safety without worrying about synchronization.
  • You are using SwiftUI, as it prefers value types for performance.

Use classes when:

  • You need reference-type behavior (shared state).
  • You require inheritance.
  • You want Objective-C interoperability (@objc features like NSNotification).
  • You need identity comparison (=== operator).

3. Can structs have methods in Swift?

Answer:
Yes, structs in Swift can have methods just like classes. However, instance methods that modify properties must be marked with mutating:

struct Car {
    var speed: Int
    
    mutating func increaseSpeed(by value: Int) {
        speed += value
    }
}

var myCar = Car(speed: 60)
myCar.increaseSpeed(by: 20)
print(myCar.speed)  // Output: 80

4. Can structs conform to protocols?

Answer:
Yes, structs can conform to protocols just like classes:

protocol Drivable {
    func drive()
}

struct Bike: Drivable {
    func drive() {
        print("Riding the bike")
    }
}

5. Can structs have inheritance in Swift?

Answer:
No, structs do not support inheritance. However, they can conform to protocols, which allows them to share behavior.


6. Can a struct have a deinitializer (deinit)?

Answer:
No, only classes can have deinit because structs are value types and do not need deinitialization.


7. How does memory management differ between structs and classes?

Answer:

  • Structs are stored in the stack and copied when assigned.
  • Classes are stored in the heap, and references are managed using ARC (Automatic Reference Counting).

8. Can structs contain reference types like classes or closures?

Answer:
Yes, structs can contain reference types like classes or closures, but then the struct may exhibit reference-like behavior:

struct Container {
    var object: SomeClass
}

If SomeClass is a reference type, changes to object will be reflected across copies of Container.


9. Can structs be used with Swift’s Codable protocol?

Answer:
Yes, structs can conform to Codable easily for JSON encoding/decoding:

struct User: Codable {
    var name: String
    var age: Int
}

let user = User(name: "Mike", age: 30)

10. How can you make a class behave like a struct?

Answer:
To make a class behave more like a struct, use a copy-on-write pattern or implement a custom copy() method.

Here are some advanced questions and answers on Struct vs. Class in Swift:


1. How does Copy-on-Write (CoW) work with Swift structs?

Answer:
Swift optimizes struct copies using Copy-on-Write (CoW). Instead of immediately copying, Swift defers the copy until a modification happens. This helps improve performance while maintaining value semantics.

Example:

struct CoWExample {
    private var data = [1, 2, 3]
    
    mutating func modifyData() {
        data.append(4) // A copy happens here only if another reference exists
    }
}

CoW ensures that structs do not duplicate data unless modified, reducing unnecessary memory overhead.


2. How does ARC (Automatic Reference Counting) behave differently for classes vs. structs?

Answer:

  • Structs do not use ARC, as they are stored in the stack.
  • Classes use ARC, which tracks references and automatically deallocates when no strong references remain.

Example with classes causing retain cycles:

class A {
    var b: B?
}

class B {
    var a: A?
}

var objA: A? = A()
var objB: B? = B()

objA?.b = objB
objB?.a = objA

// Memory leak: Both objects hold strong references to each other

To fix this, use weak references:

class B {
    weak var a: A?
}

3. What happens when you pass a struct vs. a class to a function?

Answer:

  • Structs (Value Types): A copy of the struct is passed. Changes inside the function do not affect the original struct.
  • Classes (Reference Types): A reference is passed, so changes inside the function modify the original instance.

Example:

struct ValueType {
    var x: Int
}

class ReferenceType {
    var x: Int = 0
}

// Struct behavior
func modifyStruct(_ val: inout ValueType) {
    val.x = 10
}

var myStruct = ValueType(x: 5)
modifyStruct(&myStruct)
print(myStruct.x) // 10 (copy modified)

// Class behavior
func modifyClass(_ ref: ReferenceType) {
    ref.x = 10
}

var myClass = ReferenceType()
modifyClass(myClass)
print(myClass.x) // 10 (original modified)

4. How do you prevent unintended struct copying in Swift?

Answer:
Use @_copyable (introduced in Swift 5.10) or store a reference type (like a class) inside the struct.

Example using a class inside a struct:

class ReferenceStorage {
    var value: Int
    init(value: Int) {
        self.value = value
    }
}

struct Wrapper {
    var storage: ReferenceStorage
}

var a = Wrapper(storage: ReferenceStorage(value: 42))
var b = a
b.storage.value = 100

print(a.storage.value) // 100 (both share the same reference)

5. What are resilient structs in Swift, and why do they matter?

Answer:
A resilient struct is one where Swift allows future changes without breaking binary compatibility.

  • If a struct is not resilient, its memory layout is fixed at compile-time.
  • If it is resilient, the compiler allows new fields to be added without breaking ABI (Application Binary Interface).

Example of a non-resilient struct (default behavior within the same module):

struct NonResilient {
    var a: Int
    var b: String
}

Example of a resilient struct (needed when exposing via a framework):

@frozen struct Resilient {
    var a: Int
}
  • @frozen ensures the struct layout is fixed, allowing better optimization.

6. How can you manually implement Copy-on-Write behavior for a struct?

Answer:
By using an internal reference type and checking isKnownUniquelyReferenced():

final class InternalData {
    var numbers: [Int]
    
    init(numbers: [Int]) {
        self.numbers = numbers
    }
}

struct COWStruct {
    private var data: InternalData
    
    init(numbers: [Int]) {
        self.data = InternalData(numbers: numbers)
    }
    
    mutating func modifyData() {
        if !isKnownUniquelyReferenced(&data) {
            data = InternalData(numbers: data.numbers) // Create a new copy
        }
        data.numbers.append(100)
    }
}

This ensures that a new copy is created only when needed, reducing memory overhead.


7. Can structs have computed properties and property observers like classes?

Answer:
Yes, structs can have both:

struct Rectangle {
    var width: Double
    var height: Double
    
    // Computed Property
    var area: Double {
        return width * height
    }
    
    // Property Observer
    var perimeter: Double = 0.0 {
        willSet {
            print("About to set perimeter to \(newValue)")
        }
        didSet {
            print("Updated perimeter to \(perimeter)")
        }
    }
}

8. How does performance differ between structs and classes in Swift?

Answer:

  • Structs (stack allocation) are faster and use less memory for small, simple data types.
  • Classes (heap allocation) require reference counting and can have ARC overhead when heavily used.
  • Structs with CoW are optimized for efficiency.

Benchmarking Example:

import Foundation

class ClassObject {
    var value: Int
    init(value: Int) { self.value = value }
}

struct StructObject {
    var value: Int
}

let start = CFAbsoluteTimeGetCurrent()

var objects = [StructObject(value: 0)]
for _ in 0..<1_000_000 {
    objects.append(StructObject(value: 1))
}

print("Time taken:", CFAbsoluteTimeGetCurrent() - start)
  • If you change StructObject to ClassObject, performance will decrease due to heap allocation and ARC.

9. How do Swift structs handle thread safety better than classes?

Answer:
Since structs are value types, each thread gets its own independent copy, making them naturally thread-safe.

Classes require synchronization to avoid race conditions:

class SharedResource {
    private var value = 0
    private let queue = DispatchQueue(label: "thread-safe", attributes: .concurrent)

    func increment() {
        queue.async(flags: .barrier) {
            self.value += 1
        }
    }
}

With structs, there’s no need for synchronization since each thread has a separate copy.


10. Can you use structs with Objective-C interoperability (@objc)?

Answer:
No, structs cannot be used with @objc, as Objective-C does not support value types. You must use classes:

@objc class ObjCCompatible: NSObject {
    @objc var name: String
    
    @objc init(name: String) {
        self.name = name
    }
}

Here are some more advanced questions on Struct vs. Class in Swift:


1. What happens when you mutate a struct inside a closure?

Answer:
If a struct is captured by a closure, it behaves differently based on whether the closure is escaping or non-escaping.

  • Non-escaping closure: The struct is passed directly, so mutations work as expected.
  • Escaping closure: The struct is copied, and mutations do not affect the original instance.

Example:

struct Counter {
    var value = 0
}

var counter = Counter()

let closure = { counter.value += 1 }  // Works fine
closure()
print(counter.value) // Output: 1

func testEscaping(closure: @escaping () -> Void) {
    closure()
}

testEscaping { counter.value += 1 }  // Error: Cannot capture 'counter' mutably

To fix it, use inout:

func testNonEscaping(closure: (inout Counter) -> Void) {
    closure(&counter)
}

2. Can you use a class inside a struct? How does it affect mutability?

Answer:
Yes, a struct can contain a class instance. However, since classes are reference types, they break value-type behavior.

Example:

class Engine {
    var horsepower: Int
    init(horsepower: Int) {
        self.horsepower = horsepower
    }
}

struct Car {
    var engine: Engine
}

var car1 = Car(engine: Engine(horsepower: 150))
var car2 = car1
car2.engine.horsepower = 200

print(car1.engine.horsepower) // Output: 200 (unexpected mutation!)

This happens because Car is a value type, but engine is a reference type.

To fix this, manually implement Copy-on-Write (CoW):

struct SafeCar {
    private var engineRef: Engine

    var engine: Engine {
        mutating get {
            if !isKnownUniquelyReferenced(&engineRef) {
                engineRef = Engine(horsepower: engineRef.horsepower)
            }
            return engineRef
        }
        set { engineRef = newValue }
    }
}

3. How can you enforce immutability in a class like a struct?

Answer:
Since structs are immutable by default, you can achieve similar behavior in classes using let properties and making them final:

final class ImmutableClass {
    let value: Int
    
    init(value: Int) {
        self.value = value
    }
}

This prevents modifications after initialization, just like a struct.


4. What happens when you use a struct inside a collection (e.g., Array)?

Answer:
When a struct is inside an Array, every time it is modified, a new copy is created due to Copy-on-Write (CoW).

Example:

struct Item {
    var value: Int
}

var items = [Item(value: 1), Item(value: 2)]
var copy = items  // CoW happens only when modified
copy[0].value = 100

print(items[0].value) // Output: 1

To prevent unnecessary copying, Swift uses Copy-on-Write (CoW) under the hood.


5. Can you override methods in a struct?

Answer:
No, structs do not support method overriding because they do not support inheritance.

However, you can achieve similar behavior using protocols and protocol extensions:

protocol Drawable {
    func draw()
}

struct Circle: Drawable {
    func draw() {
        print("Drawing a circle")
    }
}

6. What are the performance trade-offs of using structs over classes?

Answer:

Structs (Value Types) are Faster

  • Stored in the stack, leading to faster allocation & deallocation.
  • No ARC (Automatic Reference Counting) overhead.
  • Better cache locality, improving performance in large data structures.

Classes (Reference Types) Have More Overhead

  • Stored in the heap, leading to slower access.
  • ARC overhead due to reference counting.
  • Shared across multiple objects, which may introduce race conditions.

7. How can you prevent struct copies in a function?

Answer:
Use inout to modify the original struct instance without copying:

struct DataStore {
    var value: Int
}

func modify(_ data: inout DataStore) {
    data.value += 10
}

var data = DataStore(value: 5)
modify(&data)
print(data.value) // Output: 15

Without inout, a copy is made.


8. Can structs conform to Equatable and Hashable?

Answer:
Yes, and Swift automatically synthesizes Equatable and Hashable for structs:

struct Point: Equatable, Hashable {
    var x: Int
    var y: Int
}

let p1 = Point(x: 3, y: 4)
let p2 = Point(x: 3, y: 4)

print(p1 == p2) // Output: true

For custom comparison, implement == manually:

struct CustomPoint {
    var x: Int
    var y: Int
    
    static func == (lhs: CustomPoint, rhs: CustomPoint) -> Bool {
        return lhs.x == rhs.x && lhs.y == rhs.y
    }
}

9. How does protocol-oriented programming (POP) relate to structs?

Answer:
Swift encourages protocol-oriented programming (POP) by using structs instead of classes.

  • Since structs don’t support inheritance, Swift relies on protocols to share behavior.
  • Protocol extensions allow adding default functionality.

Example:

protocol Identifiable {
    var id: String { get }
}

extension Identifiable {
    func printID() {
        print("ID: \(id)")
    }
}

struct User: Identifiable {
    var id: String
}

let user = User(id: "12345")
user.printID() // Output: ID: 12345

This eliminates the need for class-based inheritance, making code more modular.


10. How do Swift structs behave differently in SwiftUI?

Answer:
SwiftUI relies heavily on structs because they:

  • Ensure immutability, making UI updates safer.
  • Avoid reference-type race conditions (since structs don’t share state).
  • Work well with declarative syntax.

Example of SwiftUI View as a struct:

import SwiftUI

struct ContentView: View {
    var body: some View {
        Text("Hello, World!")
    }
}

Since ContentView is a struct, every time SwiftUI needs a new UI update, it simply creates a new copy—no ARC needed!


Bonus: How can you convert a class-based model to a struct-based model safely?

Answer:
If you want to convert a class to a struct, check:
✅ Does the object need identity? → If yes, keep it a class.
✅ Does it require reference semantics? → If yes, keep it a class.
✅ Does it only store data? → Convert it to a struct.

Example Class to Struct Migration:

// Old Class
class User {
    var name: String
    init(name: String) {
        self.name = name
    }
}

// New Struct
struct User {
    var name: String
}

This ensures better performance and thread safety

Table of Contents

Index