Sheet ⁨09⁩ · ⁨Quizzes⁩Surveyed ⁨2026⁩

Swift & iOS: App Development Fundamentals

20 QuestionsIntermediatePublished: Updated:

Swift & iOS: App Development Fundamentals

Up to 20 questions, shuffled on every run

Quiz Configuration
Enterto start

Most Swift confusion traces back to a small number of ideas, and once those land the rest of the language stops producing surprises. This quiz is built around three of them.

The top band is the difference the quiz returns to most often, including why mutating through a `let` class reference compiles while the struct equivalent does not. The middle band shows that `String?` is genuinely a separate type with no `count` member, which is a stronger statement than 'it might be nil'. The bottom band is the property-wrapper choice behind the most common SwiftUI bug.

Twenty-eight questions across optionals, value and reference semantics, protocols, closures and capture lists, error handling, SwiftUI state, the app lifecycle, and structured concurrency. Every option has an explanation, not just the correct one, because knowing why the other three are wrong is usually the part that sticks.

Answer key and explanations20 questions

The quiz above draws 20 questions at random from these 28, so a second attempt will not be the same run. Everything in the pool is listed here.

  1. Why does `s.count` fail to compile when `s` is declared as `String?`

    Answer`String?` is a different type from `String` and simply has no `count` member

    `String?` is shorthand for `Optional<String>`, which is a distinct type from `String`, and it has no `count` member of its own. The compiler is not warning you about a possible nil, it is telling you the member does not exist on that type. Unwrap first with `if let`, `guard let`, `??` or optional chaining.

  2. What is the practical difference between `if let` and `guard let`?

    Answer`guard let` binds the value in the enclosing scope and its else branch must exit

    `if let` binds the value inside its own braces, so it is out of scope after the block. `guard let` binds it in the enclosing scope and requires the else branch to exit, which keeps the happy path unindented. For a precondition at the top of a function, `guard let` is almost always the clearer choice.

  3. What does the nil-coalescing operator `??` return?

    AnswerThe unwrapped left value if non-nil, otherwise the right value, as a non-optional

    `a ?? b` returns the unwrapped value of `a` when it is non-nil, and `b` otherwise. The result is non-optional, which is the point: it is how you go from `Int?` to `Int` with a fallback. The right-hand side is only evaluated when needed, so an expensive default costs nothing on the common path.

  4. When is force unwrapping with `!` genuinely reasonable?

    AnswerWhen nil would mean a programmer error, and crashing is the correct response

    It is reasonable when nil represents a programmer error you want to fail loudly on, such as a resource that is guaranteed to be bundled with the app. Writing `!` is asserting that this cannot be nil, and if you are wrong you want the crash rather than silent wrong behaviour. Using it to skip an unwrap you did not feel like writing is how you ship crashes.

  5. What is `String??` and how do you flatten it?

    AnswerA double optional, flattened with `flatMap { $0 }`

    A double optional, `Optional<Optional<String>>`. It arises when a failable operation returns something that was already optional, for example subscripting a `[String: String?]` dictionary. `flatMap { $0 }` collapses it to `String?`, and pattern matching with `case let .some(.some(v))` also works.

  6. Given `struct P { var x = 1 }`, `var a = P()`, `var b = a`, and `b.x = 99`, what is `a.x`?

    Answer1

    `a.x` is 1. Assigning a struct copies it, so `a` and `b` are independent values from that moment. This is the single most important difference from classes, and it is why Swift can offer local reasoning about a struct: nothing else holds a reference that could change it under you.

  7. Why does `let box = Box(); box.x = 99` compile when `Box` is a class?

    Answer`let` fixes the reference, and the referenced object is still mutable

    `let` makes the reference constant, not the object. You cannot reassign `box` to a different instance, but you can mutate the instance it points to. For a struct the same code would not compile, because with a value type the constant *is* the value. This difference catches almost everyone once.

  8. Which of these Swift standard library types is a reference type?

    Answer`AnyObject`

    `AnyObject` is the protocol all class types conform to, so it is the reference-typed answer. `Array`, `Dictionary`, `Set` and `String` are all structs, which is why they have copy semantics. Copy-on-write keeps that affordable: the copy is deferred until one side mutates.

  9. What does copy-on-write mean for a Swift Array?

    AnswerThe buffer is shared until a mutation happens, and only then is it duplicated

    Assigning an array shares the underlying buffer until one of the copies is mutated, at which point that copy allocates its own buffer. So the value semantics are real but the cost is deferred, and passing a large array to a function that only reads it is cheap. It also means a mutation can have a surprising one-off cost.

  10. When would you choose a class over a struct?

    AnswerWhen instances need identity, shared mutable state, inheritance, or a deinitialiser

    When you need identity or shared mutable state: two things referring to the same object and seeing each other's changes. Also when you need inheritance, deinitialisers, or Objective-C interoperability. Swift's guidance is to start with a struct and reach for a class when one of those needs is real.

  11. What is a protocol extension with a default implementation used for?

    AnswerSharing behaviour across all conforming types, including structs and enums

    It provides shared behaviour to every conforming type without inheritance, so a struct, a class and an enum can all pick it up. Conforming types may override it. This is the mechanism behind protocol-oriented programming: compose behaviour through protocols rather than inheriting it from a base class.

  12. What does an `associatedtype` in a protocol do?

    AnswerDeclares a placeholder type that each conforming type specifies

    It declares a type the conforming type gets to choose, which makes the protocol generic over that type. `Collection` uses `Element` this way. A protocol with an associated type has restrictions on being used as an existential, which is why `any Collection` behaves differently from a concrete type and why `some` and generics are usually preferred.

  13. What is the difference between `some Shape` and `any Shape` as a return type?

    Answer`some` is one concrete type hidden from the caller; `any` is a box that could hold different types

    `some Shape` is an opaque type: there is exactly one concrete type, the compiler knows it, and the caller does not. `any Shape` is an existential: the value is boxed and could be a different concrete type on each call, at the cost of dynamic dispatch. Prefer `some` when you return one type, which is the common case.

  14. In `items.map { $0 * 2 }`, what makes the braces valid without parentheses?

    AnswerTrailing closure syntax, since the closure is the last argument

    Trailing closure syntax. When a closure is the final argument, it can be written after the call's parentheses, and if it is the only argument the parentheses can be dropped entirely. `$0` is the shorthand for the first parameter. Swift 5.3 extended this to multiple trailing closures with labels.

  15. Why is `[weak self]` needed in a closure a view controller stores as a property?

    AnswerTo break a retain cycle between the controller and the closure it stores

    The controller holds the closure strongly, and the closure captures `self` strongly, so neither can ever be deallocated. That is a retain cycle, and it leaks the entire view controller and its view hierarchy. `[weak self]` makes the capture non-owning, so `self` becomes an optional you unwrap inside, usually with `guard let self else { return }`.

  16. What does `@escaping` on a closure parameter mean?

    AnswerThe closure may be stored and called after the function has returned

    The closure may be stored and called after the function returns, so it escapes the function's lifetime. Swift requires the annotation because a non-escaping closure can be optimised more aggressively and cannot create a retain cycle. Storing a non-escaping closure in a property is a compile error, which is the compiler protecting you.

  17. What is the difference between `try?` and `try!`?

    Answer`try?` turns an error into nil; `try!` crashes if an error is thrown

    `try?` converts a thrown error into `nil`, giving you an optional and discarding the error entirely. `try!` asserts no error will be thrown and crashes if one is. `try?` is right when you genuinely do not care why it failed; if you do care, use `do/catch` so the error is not silently swallowed.

  18. What does `defer` guarantee inside a function?

    AnswerIt runs on every exit from the scope, including a thrown error

    The block runs when the scope exits, whichever way it exits: a normal return, an early return, or a thrown error. Multiple defer blocks run in reverse order of declaration. It is how you keep cleanup next to the acquisition it pairs with, instead of duplicating it before every return.

  19. What is `Result<Success, Failure>` used for now that Swift has async/await?

    AnswerRepresenting success or failure as a value you can store and inspect later

    It represents an outcome as a value you can store, pass around and pattern match later, which `throws` cannot do because a thrown error must be handled immediately or propagated. It is still the right tool for caching an outcome, for a completion-handler API you cannot change, and for collecting many results where some failed.

  20. Why can a SwiftUI `View` struct not simply store mutable state in a normal `var`?

    AnswerThe struct is thrown away and rebuilt on every render, so plain properties do not survive

    SwiftUI discards and recreates the view struct whenever it needs to re-render, so anything stored in it is lost. The `body` is also read-only, so mutating a plain property from it will not compile. `@State` solves this by storing the value outside the struct, in storage SwiftUI manages and keys to the view's position in the hierarchy.

  21. When do you use `@StateObject` rather than `@ObservedObject`?

    Answer`@StateObject` when this view creates and owns the object, so it survives rebuilds

    `@StateObject` when this view creates and owns the object: SwiftUI instantiates it once and keeps it across rebuilds. `@ObservedObject` when the object is created elsewhere and passed in. Using `@ObservedObject` for something the view creates means a new instance on every rebuild, which loses state in a way that looks like a random bug.

  22. What does the `$` prefix on a SwiftUI state property give you?

    AnswerA `Binding`, giving read-write access to the underlying storage

    The projected value, which for `@State` is a `Binding` to the underlying storage. A `Binding` is read-write, so a `TextField` can change the value the parent owns. `name` is the value; `$name` is the two-way connection to it. Reading a value needs no `$`; writing it from a child does.

  23. In a UIKit view controller, when is `viewDidLoad` called?

    AnswerOnce, after the view hierarchy loads, and never again for that instance

    Once, after the view hierarchy is loaded into memory, and never again for that instance. It is the place for one-time setup. `viewWillAppear` runs before every appearance, so anything that must refresh each time the screen is shown belongs there, not in `viewDidLoad`.

  24. What replaced `AppDelegate` methods as the entry point in a SwiftUI lifecycle app?

    AnswerThe `App` protocol with a `@main` type returning a `Scene`

    The `App` protocol, with a type marked `@main` returning one or more `Scene` values. Lifecycle events that used to live in `AppDelegate` are handled with `.onChange(of: scenePhase)` and similar. You can still adopt an `AppDelegate` with `@UIApplicationDelegateAdaptor` when a framework requires one.

  25. What guarantee does an `actor` give you?

    AnswerIts mutable state is accessed by one task at a time, so it cannot be raced

    It serialises access to its own mutable state, so only one task touches that state at a time and data races on it are impossible. The cost is that calls into it from outside are asynchronous and must be awaited. It is Swift's compiler-checked replacement for remembering to take a lock.

  26. What does `@MainActor` on a method mean?

    AnswerThe method is guaranteed to run on the main thread, checked by the compiler

    It guarantees the method runs on the main actor, which is the main thread, and the compiler enforces it rather than leaving you to remember a dispatch. Anything touching UIKit or SwiftUI view state belongs there. Calling it from a background context requires an `await`, which is the compiler making the hop explicit.

  27. What is the difference between `async let` and a `TaskGroup`?

    Answer`async let` is for a fixed known set of child tasks; a TaskGroup handles a dynamic number

    `async let` starts a fixed, statically known set of concurrent child tasks that you await individually, which is ideal for two or three parallel fetches. A `TaskGroup` handles a dynamic number, letting you add tasks in a loop and collect results as they finish. Both provide structured concurrency, so cancellation and errors propagate correctly.

  28. What happens to an unstructured `Task { }` when the view that created it disappears?

    AnswerIt keeps running, unless you retain the handle and cancel it yourself

    Nothing automatic. An unstructured `Task` has no parent, so it is not cancelled when the surrounding scope ends and it keeps running. You have to hold the handle and call `cancel()` yourself, or use `.task { }` in SwiftUI, which ties the task's lifetime to the view and cancels it on disappearance.

Was this useful?

You might also enjoy

More posts on similar topics

C# & .NET: Language and Runtime Fundamentals

C# & .NET: Language and Runtime Fundamentals

Ready to test how well you know C# and the .NET runtime? This quiz walks through the type system, LINQ, async/await, the CLR and garbage collection, and modern features like records and pattern matchi

Go: Programming Fundamentals & Concurrency

Go: Programming Fundamentals & Concurrency

Welcome to the Go (Golang) Basics Quiz! Most modern infrastructure tooling is written in Go, largely because of its compiled performance and built-in concurrency. This quiz will test your understandin

PHP & Laravel: Modern Web Development Fundamentals

PHP & Laravel: Modern Web Development Fundamentals

Ready to check how well you know PHP and Laravel? This quiz runs through routing and middleware, Eloquent relationships and the N+1 trap, migrations and seeders, Blade, and the artisan workflow. Read

Kotlin: Modern JVM Language Fundamentals

Kotlin: Modern JVM Language Fundamentals

Kotlin reads like a friendlier Java for about a week, and then you meet the parts that are genuinely different: nullability living in the type system, coroutines with a scope that owns its children, a

Java: Core Language & JVM Fundamentals

Java: Core Language & JVM Fundamentals

Java has quietly powered banks, Android, and countless backend systems for decades, and the language keeps evolving with records, sealed classes, and virtual threads. This quiz walks through the core

Prometheus & Grafana: Monitoring & Observability Fundamentals

Prometheus & Grafana: Monitoring & Observability Fundamentals

Welcome to the Prometheus & Grafana Basics Quiz! Monitoring and Observability are the heart of a stable production environment. This quiz will test your knowledge on how metrics are collected, how to

6 related posts