Memos
163 sheets
ai
- AI coding agents at work — the senior iOS way
An agent (Claude Code, Codex, Cursor, Copilot, Xcode’s coding assistant) drafts and executes; you stay accountable for every line that ships. The senior skill is not prompting — it is specifying the task, constraining the context, and verifying each small change with the compiler, the tests, the running app and Instruments. Speed comes from short verified loops, not big generated diffs.
- Core ML · Vision · on-device ML (+ Core AI, iOS 27)
Core ML runs a trained model file on the device and splits its graph across CPU, GPU and Neural Engine; Vision, Natural Language and Speech are task frameworks on top (Apple’s models, or yours); Create ML and coremltools make the file. iOS 27 adds Core AI for modern neural nets. The data never leaves the phone — the model ships in your bundle.
architecture
- Coordinator · Repository · DI · Clean / VIPER
Four ways to stop one object knowing too much. Coordinator — where to go next (navigation out of VCs). Repository — where data comes from (hide network/cache/DB behind one protocol). DI — who builds my collaborators (someone outside, at the composition root). Clean — which way dependencies point (always inward, towards business rules).
- GoF: behavioural patterns
Behavioural patterns decide who talks to whom and who decides: Strategy (client picks the algorithm) · State (object switches itself) · Observer (one-to-many notify) · Command (request as object, execute+undo) · Template Method (base skeleton, subclass hooks) · Chain of Responsibility (pass until handled) · Mediator (hub instead of mesh) · Iterator (next() until nil).
- GoF: creational + structural
Creational patterns decide how an object comes to exist (Singleton, Factory Method, Abstract Factory, Builder). The four “wrappers” are told apart by intent, not shape: Adapter changes the interface, Decorator keeps it and adds behaviour, Facade simplifies a whole subsystem, Proxy keeps it and controls access.
- iOS client system design — “design a feed”
Drive the dialogue: clarify (functional, non-functional, scale) → API + data model → high-level client architecture → data flow (read + write) → 2–3 deep dives → trade-offs. Design the client, not the backend: the local store is the source of truth, the network syncs into it.
- MVC · MVP · MVVM · MVVM-C
All four keep the same Model; they differ in where presentation logic lives and who holds a reference to whom. Each step moves logic out of the UIKit-bound view into a plain, unit-testable object: MVC (controller does it all) → MVP (presenter pushes to a view protocol) → MVVM (view binds to state; VM never sees the view) → MVVM-C (a Coordinator also takes navigation out).
- SOLID — five principles, exact words
Single responsibility · Open/closed · Liskov substitution · Interface segregation · Dependency inversion — Robert C. Martin’s rules for code whose parts can change independently. Interviewers want the exact definition, one iOS example, and which pattern serves it.
- System design — analytics SDK & E2E-encrypted messenger
Same framework, two very different centres of gravity: scope → functional → non-functional → data model → API → high-level → deep dive. An SDK lives inside someone else’s app: it must never crash, block, bloat or leak it — the design is a persisted, batched, privacy-gated queue. An E2EE messenger’s server is an untrusted mailbox: keys live only on devices, every message is sealed per recipient device, and the push extension has to decrypt.
ble
- BLE in the Apple ecosystem — iBeacon, ASK, UWB, Find My
Apple’s proximity features are different frameworks on the same radio: iBeacon is a BLE advert read by Core Location (not CoreBluetooth); AccessorySetupKit (iOS 18) pairs one accessory without the broad Bluetooth permission; Nearby Interaction measures distance + direction over UWB after BLE hands over tokens; Find My is a crowd relay of encrypted locations keyed by rotating BLE keys. Plain BLE GATT needs no MFi.
- Bluetooth Mesh · Thread · Matter
Bluetooth Mesh floods small messages over BLE advertising (no connections) with pub/sub group addresses; Thread is a routed IPv6 mesh on 802.15.4 reaching the LAN through a border router; Matter is the application layer (data model + security + commissioning) that runs over Thread, Wi-Fi or Ethernet — commissioned over BLE, local-first, multi-admin.
c
- Bits, masks and hardware registers in C
A register is a memory address you reach through a volatile uint32_t *; you change one field with a mask + shift and must keep every other bit intact — |= sets, &= ~ clears, ̂= toggles, (r >> n) & 1u reads. The dangerous part is not the operator, it is the read-modify-write.
- Memory layout · padding · endianness · integer promotion
Code and constants sit in flash (.text, .rodata); initialised globals in .data (RAM, with an image in flash), zero ones in .bss (RAM, zeroed at boot, no flash); malloc in the heap; locals on the stack. Struct members sit at multiples of their alignment → padding. Byte order in memory is the endianness. Arithmetic on anything narrower than int happens in int.
- Pointers · const · restrict · decay · dangling
Read a declaration from the name outward: right first ([], ()), then left (*, const). const guards what is immediately left of it (or right, if it is first). restrict promises “this pointer is the only way to that memory”. A pointer is valid only while its object lives.
- Undefined behaviour · the build: preprocess, compile, link
UB = the standard places no requirements on the result — and the optimiser assumes it never happens, so it deletes checks and rewrites code around it; the bug shows up far away, often only at -O2. Building = per translation unit preprocess → compile → assemble to a .o, then link every .o + library into one image with real addresses.
- volatile · _Atomic · sharing state with an ISR or another core
volatile is a promise about the compiler: every read and write in the source becomes a real load/store, never cached in a register or removed. It gives no atomicity, no ordering of ordinary memory and no CPU barrier. State shared by a task and an ISR or another core needs _Atomic or a critical section.
cross-platform
- Android lifecycle, ViewModel & Jetpack Compose
An Activity is destroyed and recreated on every configuration change (rotation, locale, dark mode, window size); the ViewModel survives that, the saved-state Bundle survives process death, and Compose redraws UI as a function of state — re-running only the composables that read a changed State.
- KMP in practice — library stack, shared architecture, concurrency
A shared module is repositories + use cases + a state holder exposing StateFlow in commonMain, built on Ktor, kotlinx.serialization, SQLDelight, Koin and kotlinx-datetime, with per-platform engines and drivers; each platform binds native UI to it. On iOS, Dispatchers.Main is the main queue and the new memory model (tracing GC, no freezing) lets objects cross threads like on the JVM.
- Kotlin coroutines & Flow · Android vs iOS platform
A coroutine is a suspendable computation launched in a scope whose Job tree gives structured concurrency (a parent waits for and cancels its children); cancellation is cooperative. Flow is a cold stream, StateFlow/SharedFlow are hot — the Kotlin twins of async/await, Task, AsyncSequence and Combine subjects.
- Kotlin Multiplatform — fundamentals & the Swift boundary
KMP compiles one Kotlin codebase to each platform’s native artifact — JVM bytecode for Android, a Kotlin/Native (LLVM) binary shipped as an Objective-C framework for iOS — so you share logic (models, networking, persistence, state) and keep native UI unless you opt into Compose Multiplatform. Swift sees Kotlin through an ObjC header: that header is where every interop trap lives.
cs
- Cross-language FFI — everything meets at the C ABI
Languages call each other through the C ABI: an unmangled symbol, the platform’s C calling convention, C types (scalars, pointers, plain structs). One side exports C-looking functions (extern "C", Go //export, Swift @c); the other links them (-l, cgo, module map) or dlopens them and declares the types (ctypes, Ruby FFI). Every allocation keeps one owner.
- Cryptography with CryptoKit — AEAD, ECDH, signatures, KDFs
Symmetric AEAD (AES-GCM, ChaCha20-Poly1305) encrypts and authenticates bulk data with one shared key and a unique nonce; asymmetric keys (P-256, Curve25519) agree on that key (ECDH → HKDF) and sign (ECDSA, Ed25519). CryptoKit gives vetted primitives with safe defaults — your job is choosing them, managing keys and nonces, and never inventing a primitive, mode or protocol.
- Data structures — layout, invariant, cost
Every structure is a memory layout plus an invariant that makes some operations cheap and others expensive. Pick by the operation that must be fast, then check the constants: contiguous memory + cache lines beat pointer chasing at equal big-O on every modern CPU.
- Designing data structures — compose for O(1)
Name the operation that must be O(1), pick the structure whose invariant makes it cheap, and when no single structure does all of them, compose two that index the same nodes (hash map for find, list for order; heap for extreme, array for random). Say what you pay: memory, amortised vs worst case, thread safety.
- Distributed system design — the toolkit
Scale by making servers stateless and adding them behind a load balancer; take read load off the database with caches and read replicas; take write and storage load with sharding; move slow work onto queues. Each step buys scale with a named cost — staleness, replication lag, hot keys, duplicates — and a senior answer names the cost and the number that justifies it.
- Hashing, deep — tables, crypto, rings, passwords, Bloom
A hash maps arbitrary input to a fixed-size value. Table hashes want speed + uniformity and a secret seed against flooding (Swift: SipHash, reseeded per process); cryptographic hashes want one-wayness and collision resistance (SHA-256); password hashes want to be slow and memory-hard; a MAC needs a key. Choosing the wrong family is the classic mistake.
debugging
- Debugging Swift Concurrency — pool, actors, executors
Tasks run on the cooperative thread pool (~one thread per core, never grows) or on the main actor’s main thread. The runtime’s contract is forward progress: await suspends and frees the thread; a blocked thread is simply lost. Three failures follow — a blocked main actor (hang), actor contention (parallel code turned serial), pool starvation (threads parked in waits). Concurrency instruments show task/actor/executor state; System Trace shows why a thread is not running.
- Logging, signposts & power — from code to field data
Logger writes words into the OS’s unified log (cheap, levelled, private by default), OSSignposter writes time (intervals and events Instruments draws as tracks), and the OS itself measures energy. Read it live (Xcode console, Console, log), recorded (Instruments, Power Profiler, xctrace), or aggregated from users (MetricKit, Organizer).
- Sanitizers & runtime checkers — make the bug crash here
Memory and threading bugs corrupt silently and crash later, elsewhere. Sanitizers are compiler instrumentation + a runtime that check every access as it happens (ASan: is this byte addressable? TSan: is there a happens-before edge?) and stop at the first bad access with the stacks that explain it. Runtime checkers (Main Thread Checker, Thread Performance Checker, exclusivity) need no special build. Price: slowdown — so they live in Debug schemes, test plans and CI, never in the shipped app.
- SwiftUI instrument · Cause & Effect · hitches & hangs
The Xcode 26 SwiftUI instrument times every SwiftUI update and flags the long ones (orange / red = likely to cause a hitch or hang); its Cause & Effect Graph shows why each body ran, from the gesture or state change on the left to the view bodies on the right. A body that overruns the frame deadline = a hitch; a main thread that cannot answer a discrete input for 250 ms+ = a hang.
design
- API design — HTTP resources and Swift APIs
An API is a contract you can never fully take back: model resources (nouns) and let methods carry the verbs, make every write safe to retry, make every change additive, and judge a Swift API by clarity at the point of use. Old app versions live for years.
- Code smells → refactorings
Refactoring (Fowler): “a change made to the internal structure of software to make it easier to understand and cheaper to modify without changing its observable behaviour”. A smell is a surface symptom that suggests a deeper problem — name the smell, name the refactoring, and say how you keep behaviour pinned: tests first, tiny steps, commit each green step.
- Concurrency patterns — the shapes, and their Swift form
Every concurrent design answers two questions: who may touch this state (one owner + messages, or many + a lock) and what happens when one side is faster (a bounded buffer, backpressure, or dropping). The named patterns — producer–consumer, pool, actor, pipeline, fan-out/fan-in, reader–writer — are stock answers; Swift gives most of them a compiler-checked form (actor, TaskGroup, AsyncStream, Sendable).
- Data access patterns — under the Repository
A Repository is only the front door. Behind it: DTOs mapped to domain models at an anti-corruption edge, a local DB as the single source of truth the UI observes, a network gateway that only refreshes it, a unit of work committing changes together, a cache policy per data type, and cursors for paging. Offline-first: reads never wait for the network; writes go through an outbox.
- Dependency injection — the deep version
DI = a type receives its collaborators instead of creating or finding them. Senior content: where the graph is built (one composition root), how long each object lives (scopes; never long-lived holding short-lived), who may ask a container (only the root — anywhere else it is a service locator). Environment and @Dependency are ambient DI: handy, but the dependency leaves the init.
- Design principles — the rules beneath the patterns
Patterns trade indirection for changeability; these principles say what changeable code looks like: high cohesion, low coupling (named by kind — connascence), talk to friends, tell don’t ask, compose, one home per piece of knowledge, build only what’s needed, the right class for each responsibility (GRASP), dependencies toward stability.
- Functional design in Swift
Push decisions into pure functions over immutable values, push effects to a thin imperative shell, and use the type system (enums, smart constructors) so that illegal states cannot be written down. In Swift FP is a strong default, not a religion: local mutation of values is fine; the frameworks are object-oriented.
- GoF: Prototype · Composite · Bridge · Flyweight
Prototype (creational) — make a new object by copying a configured instance. Composite — leaf and group behind one interface, a tree the client walks uniformly. Bridge — split abstraction from implementation so two axes vary independently (M×N →M+N). Flyweight — share immutable intrinsic state, pass extrinsic state in. In Swift, value types + CoW are Prototype, and an enum holding [Self] is Composite.
- GoF: Visitor · Memento · Interpreter (+ Null Object)
Visitor — add operations over a stable set of element types without editing them, via double dispatch. Memento — capture an object’s state in an opaque snapshot so it can be restored (undo) without breaking encapsulation. Interpreter — one class per grammar rule; evaluate the AST. Null Object — a do-nothing implementation instead of nil checks. In Swift the first three usually become enum + exhaustive switch, value snapshots, and an indirect enum.
- Hexagonal · Onion · Modular monolith
Put the domain in the middle and make everything else — UI, HTTP, database, clock, push — a replaceable adapter plugged into a port the application defines. Hexagonal (Cockburn 2005), Onion (Palermo 2008) and Clean (Martin 2012) are one dependency rule in three vocabularies: source dependencies point inward. A modular monolith applies the same boundaries between modules in one deployable, before (or instead of) paying for microservices.
- iOS idioms and anti-patterns
Cocoa gives you a small set of communication idioms — delegate, data source, target–action, closure, NotificationCenter, KVO, responder chain — and choosing one is a question of how many listeners, whether the sender needs an answer, and who owns whom. Most iOS anti-patterns are one mistake: responsibilities and dependencies that nobody can see.
- Patterns already inside Apple’s frameworks
You use GoF patterns daily: the responder chain is Chain of Responsibility, target–action/UndoManager is Command (+ Memento), NotificationCenter/KVO/Combine are Observer, class clusters are Abstract Factory, appearance() is a Proxy, a ViewModifier a Decorator. Senior answer = pattern + API + mechanism + trade-off.
- Swift idiom patterns — beyond GoF
Swift turns runtime patterns into compile-time ones: a phantom type/tagged ID makes a wrong argument a compile error, an enum makes an illegal state unrepresentable, a generic constraint is a Strategy chosen at build time — and type erasure is the price of going back to runtime. Order of preference: some/generics > any P<X> > a hand-written AnyX.
- The Composable Architecture (TCA)
Point-Free’s library for unidirectional Swift apps: a feature is a @Reducer — value-type State, an Action enum, a body that mutates state and returns Effects; a Store runs it; features compose by embedding child state/actions; the outside world comes in through @Dependency; TestStore proves every step. Current 1.26.2 (2026-08-28).
- Unidirectional data flow & state machines
State lives in one place; the view renders it and sends actions; a pure reducer computes the next state and returns effects (side effects as values) whose results come back as actions — one loop, one direction (Elm → Redux → TCA). A state machine constrains which transitions exist; statecharts add hierarchy, parallel regions and guards. Both aim at “make illegal states unrepresentable”.
- VIP · RIBs · MVI · Redux · MV — the architecture map
Every iOS architecture answers three questions: who owns the state, which way does data flow, and where do side effects live. VIP (Clean Swift) makes each screen a one-way cycle of three objects; RIBs (Uber) drive the app by a tree of business logic, not of views; MVI and Redux keep one immutable state changed only by a reducer; TCA is Redux made composable; MV says SwiftUI views plus @Observable models are already enough.
dev-tools
- Vim — the editing language
Vim is modal: Normal mode is a language — [count] operator [count] motion|text-object (d2w, ci", gUip); an operator typed twice acts on the line (dd, >>), and . repeats the last change. Insert is only for typing, Visual selects then operates, : runs Ex commands over line ranges.
engineering
- Feature flags, experiments & mobile observability
Ship code dark behind a flag, turn it on for a hashed, random slice of users, log who was exposed, compare their metrics with control while guardrails (crashes, hangs, revenue) watch for harm, then ramp, hold or kill — without a new binary. On mobile this matters twice: a shipped build lives for months and the store rollout cannot be rolled back.
interview
- Algorithm patterns — if it says X, reach for Y
Don’t invent — recognise. Read the constraints first: n≤20 → exponential/backtracking is fine; n≤104 → O(n2) ok; n≥105 → you need O(n log n) or O(n); “O(log n)” or n≈109 → binary search. Then match the wording to a pattern below, say the brute force, name the repeated work, and swap in the pattern that removes it.
- Behavioural interview — the STAR method
Every behavioural answer is a ∼2-minute story: Situation (context, 1–2 sentences) · Task (your responsibility) · Action (what I did — the bulk) · Result (a number or concrete outcome) · then the reflection (what I learned / would do differently). They are not scoring the project — they are scoring you: ownership, influence, judgement, growth.
- iOS system design — the deep dives
After the boxes-and-arrows, the interviewer picks one box and asks “how exactly?”. Score by naming the mechanism: the cache key and eviction, the reconnect state machine, the idempotency key, the ordering authority, the conflict policy and how you detect a conflict, the cursor, the flag’s default when nothing was fetched, and what leaves the device privately.
- Live coding & take-home — the game plan
They grade how you think, out loud, not only the final code: clarify → examples → brute force (say it) → optimise → code → test with the examples → complexity. A mostly-working solution with clear narration beats a silent perfect one. In iOS exercises the extra marks are for the platform hygiene: [weak self], main-thread UI, no !, cancellation, loading/error states.
ios-platform
- Accessibility & Localization
Accessibility: VoiceOver walks a tree of elements and speaks each one’s label, value, traits, hint; text must scale with Dynamic Type; honour user settings (Reduce Motion, contrast). Localization: every user-visible string is a whole sentence in a String Catalog, arguments are positional, plurals follow CLDR rules, layout is leading/trailing, and numbers/dates go through formatters.
- App Clips & Handoff / Continuity
Both ride on NSUserActivity. An App Clip is a small, separately built slice of your app that a URL (from a code, tag, link or map) launches without installing the app; Handoff moves your own current activity to your own nearby device (same iCloud account, BLE + Wi-Fi). Neither is a deep link, and neither is state restoration.
- App extensions — own process, shared disk
An extension is a .appex inside the containing app, launched by the system for a host app in its own process — own sandbox, bundle id, entitlements, a far smaller memory limit. It shares no memory with its app: they meet only in an App Group, a Keychain access group, or through the system.
- App Intents · Siri, Shortcuts, Spotlight, widgets (iOS 16 → 27)
App Intents (iOS 16+) exposes an app action to the system as plain Swift: a struct conforming to AppIntent with @Parameters and an async perform(). Write it once; Siri, Shortcuts, Spotlight, the Action button, interactive widgets, Control Center and visual intelligence all run the same type — no .intentdefinition file, usually no extension.
- App size & energy — thinning, ODR, what drains the battery
Size: the App Store slices one universal upload into per-device variants, On-Demand Resources move optional content out of the download, bitcode is gone. Energy: do less work, less often, in bigger batches, woken by events not timers, at the lowest fidelity the user accepts — the radio tail, GPS and wakeups cost more than bytes.
- AVFoundation — camera capture + audio session
AVCaptureSession is a graph: devices → inputs → session → outputs, configured and started on a private serial queue (startRunning() blocks), frames delivered on another serial queue, UI on main. AVAudioSession is policy, not playback: category + mode + options tell iOS whether you mix, duck, obey the silent switch, record, and keep playing in the background.
- Background execution
After the user leaves, an app gets a few seconds and is then suspended: in memory, no CPU. Every way of running later is either a short grant (beginBackgroundTask), a request the system schedules when it chooses (BGTaskScheduler, silent push), a hand-off to a daemon (background URLSession), or a declared mode that holds only while it is really in use (audio, location, …). None of them is guaranteed.
- Core Data migrations & SwiftData — deep
On open, Core Data compares the store’s recorded entity version hashes with the current model; if they differ it must migrate — lightweight (mapping inferred, schema-shaped changes only) or custom (mapping model + NSEntityMigrationPolicy code), one hop at a time. SwiftData (iOS 17) is a macro layer on the same engine: @Model classes, a ModelContext per actor, PersistentIdentifier across actors, VersionedSchema + SchemaMigrationPlan for the same lightweight/custom stages.
- Core Location + MapKit
Core Location fuses GPS/Wi-Fi/cell into fixes under a permission the user can shrink any time (While Using/Always and Precise/Approximate); the senior skill is picking the cheapest service that answers the question. MapKit draws it: annotations = points (reused views), overlays = shapes (renderers).
- Deep links · Universal Links
A Universal Link is a plain https:// URL that opens your app only if the domain has proved, via its apple-app-site-association file, that it trusts your app ID; otherwise it opens the website. A custom scheme (myapp://) is unverified, can be claimed by any app, and fails when the app is missing. Every entry point should parse to one route and hand it to one router.
- Foundation — dates, calendars, formatters
A Date is an instant: a Double of seconds since 2001-01-01 00:00 UTC — no zone, no calendar, no locale. Calendar + TimeZone + Locale are the lens that turns it into a human day and does the arithmetic; formatters are the (costly) bridge to strings — POSIX/ISO 8601 for machines, localized styles for people.
- Foundation — the essential types
Most Foundation bugs come from doing by hand what a type already does correctly: build URLs with URLComponents, format numbers with a formatter/.formatted, convert with Measurement — and respect the sandbox lifecycle: Caches and tmp are purgeable, the container path changes, Data slices keep their parent’s indices.
- Keyboard + text input
The keyboard belongs to the first responder: iOS shows its inputView (default: the system keyboard) + inputAccessoryView, and your layout moves — with keyboardLayoutGuide (iOS 15) or from the real end frame in the notifications. Input is shaped by traits, and is provisional while an IME holds marked text.
- Liquid Glass & new SwiftUI (iOS 26 · 27)
Liquid Glass is a system material for the functional layer (tab bars, toolbars, sheets, key controls) that floats above a full-bleed content layer; it lenses and adapts to what is under it. Standard components get it free with the Xcode 26 SDK; custom views opt in with .glassEffect(), grouped in a GlassEffectContainer so shapes blend and morph.
- Operation & OperationQueue — deep
An Operation is a single-shot, KVO-observable unit of work; an OperationQueue starts it when isReady (every dependency isFinished), caps parallelism with maxConcurrentOperationCount, and drops it when it posts isFinished. Cancellation is a flag, dependencies are ordering only (no data), and an async subclass must drive its own state with KVO — or it never finishes.
- Push notifications (APNs)
Your server never talks to the phone: it POSTs JSON over HTTP/2 to APNs, addressed by a device token (one app install on one device, one environment) and authenticated by your key. The app only asks permission, obtains and uploads the token, and reacts to delivery.
- Real-time calls on iOS — WebRTC, CallKit, PushKit
A call is two planes: signalling (your server, any transport — usually a WebSocket — carrying SDP offer/answer and ICE candidates) and media (WebRTC: ICE finds a path through NATs with STUN, falls back to a TURN relay, then DTLS-SRTP encrypts audio/video peer-to-peer). On iOS the app is usually not running when the call arrives: a PushKit VoIP push wakes it, it must report the call to CallKit at once, and CallKit owns the system call UI and the audio session.
- Scenes, multiwindow + state restoration
Since iOS 13 one process drives many UI instances: each window is a UIWindowScene with its own lifecycle, backed by a persistent UISceneSession. The system may disconnect a background scene to save memory and reconnect it later; restoration hands the scene back its own NSUserActivity so it rebuilds UI state (ids, not model data) — unless a deep link says where to go.
- StoreKit 2 · in-app purchases & subscriptions
StoreKit 2 (iOS 15+) is async/await: fetch Products, await product.purchase(), get back a JWS-signed Transaction wrapped in VerificationResult — verify → grant → finish(). Everything that happens outside your purchase call (renewals, refunds, Ask to Buy, other devices) arrives on Transaction.updates, which you listen to from launch.
- UICollectionView — compositional layout, registrations, configurations
A compositional layout (iOS 13) is declared, not computed in delegate callbacks: items sit in groups (the repeating unit), a group repeats along a section, sections stack into the layout. Every size is .absolute, .estimated or .fractional of its container. Cells are dequeued from registrations and rendered from content configurations (iOS 14).
- watchOS & visionOS — the two other platforms
watchOS: a separate bundle on a separate device — glance-first, mostly suspended, talks to the phone through WatchConnectivity (four channels with different delivery guarantees) or the network itself. visionOS: SwiftUI scenes placed in space — windows, volumes, immersive spaces — driven by gaze + pinch, where the system, never the app, knows where the user looks.
- WidgetKit · Live Activities · Dynamic Island
A widget is not a live view: a separate extension process hands WidgetKit a timeline of pre-rendered entries, the system swaps them at their dates and decides when to ask again (budgeted). A Live Activity is the same WidgetKit/SwiftUI surface fed by ActivityKit state updates — local or by APNs push — for a bounded event (8 h).
ios-swift
- App & view-controller lifecycle — exact order
A VC’s view is lazy: first access to .view runs loadView then viewDidLoad (once); every show runs viewWillAppear → viewIsAppearing → layout → viewDidAppear; every hide runs viewWillDisappear → viewDidDisappear; deinit last. The app moves Not running → Inactive ⇄ Active → Background → Suspended, reported per scene since iOS 13.
- ARC · strong / weak / unowned · capture lists
ARC = the compiler inserts retain/release; an object is freed deterministically the instant its strong count hits 0 (deinit runs). No tracing GC ⇒ no cycle detection: two objects that strongly own each other leak forever, silently. You break cycles by making the back-edge weak or unowned.
- Auto Layout · priorities · UIStackView · self-sizing cells
Every constraint is one linear equation item1.attr {=,≤,≥} multiplier × item2.attr + constant — the engine solves them all so each view gets exactly x, y, width, height. Too few = ambiguous; contradictory required ones = unsatisfiable. Priorities decide who yields: 1000 / 750 / 250.
- Concurrency · GCD · async/await · actors
GCD submits closures to queues and blocks threads to wait; Swift Concurrency suspends tasks at await (the thread is freed) and lets the compiler prove safety with actors (serialised state), @MainActor (UI) and Sendable (what may cross). Rule zero in both: UIKit only on the main thread.
- Instruments · measure-first performance workflow
Measure, don’t guess: baseline on a real device in the Release/Profile config → pick the instrument that answers this symptom → change one thing → re-measure the same scenario. Interviewers grade whether you can name the tool, read its number, and say what it cannot see.
- Locks & synchronisation primitives — Apple platforms
A lock makes a critical section mutually exclusive and has an owner (the thread that took it, which must release it); a semaphore is a counter with no owner; a serial queue or actor serialises by ownership instead. Pick by: is there an await inside? how short is it? do you need a count? Then: never block the cooperative pool, never hold a lock across a call-out.
- Navigation & presentation — push vs present
Push adds a VC to a UINavigationController’s viewControllers array (drill-down, back button, pop). Present starts a separate presentation chain linked by presentedViewController / presentingViewController (a modal task you finish with dismiss). The modalPresentationStyle decides whether the presenter’s view leaves the screen — and so whether it gets viewWillDisappear/viewWillAppear.
- Observation · Combine · cancellables
Four ways to observe on iOS — NotificationCenter (broadcast bus), KVO (one NSObject property), Combine (typed streams + operators + demand), @Observable (per-property read tracking, iOS 17). Every one hands you a lifetime object; the interview is about who owns it and whether the closure owns you back.
- Persistence — pick the store, respect the context
Secrets → Keychain; small settings → UserDefaults; blobs → files in the right sandbox directory; a queryable object graph → Core Data / SwiftData; recomputable in-memory → NSCache. In Core Data every NSManagedObjectContext is bound to one queue: touch it and its objects only inside perform, and hand NSManagedObjectIDs — never objects — across queues.
- Protocols, generics & method dispatch
A protocol is a contract; an extension adds shared behaviour. Only requirements (declared in the protocol body) go into the witness table and dispatch dynamically — a method that lives only in the extension is picked statically from the declared type. some P = one hidden concrete type (static); any P = a box holding any conformer (dynamic).
- SwiftUI state & data flow
A view is a cheap struct rebuilt constantly; its state lives outside it in SwiftUI’s storage, keyed by view identity. The only question each wrapper answers is who owns the truth: this view (@State, @StateObject) or someone else (@Binding, @ObservedObject, @EnvironmentObject).
- Table views, cell reuse & diffable data source
A list keeps only the visible cells alive and recycles them — so a dequeued cell is used, never blank. A diffable data source replaces index math: you describe the whole desired state as a snapshot of unique Hashable IDs, apply it, and UIKit computes the inserts/deletes/moves.
- URLSession & networking
URLSession moves bytes; you judge them: it throws only on transport failure — a 404/500 arrives as success, so check the status, then decode. Three layers, three error types, and UI back on the main thread.
- Value vs reference · mutating · copy-on-write
A value type (struct, enum, tuple) is copied on assignment / argument passing — every variable owns an independent value. A reference type (class, actor, closure) is shared — assignment copies the pointer, all variables see one object with an identity (===). Default to struct; reach for class when you need identity.
iot
- CoAP & LwM2M — REST and device management for constrained devices
CoAP (RFC 7252) is REST over UDP: GET/POST/PUT/DELETE and HTTP-like codes in a 4-byte binary header, reliability by confirmable messages with exponential backoff, push by Observe, big bodies by block-wise, security by DTLS or OSCORE. LwM2M (OMA SpecWorks) is device management on top of CoAP: a standard object/instance/resource tree (/3/0/3 = firmware version) plus bootstrap, registration, management and reporting interfaces.
javascript
- Async JavaScript — promises, await, control
A Promise is a settle-once box for a future value; its reactions always run later, as microtasks, which the loop drains completely after each task. async/await is sugar over then: await always suspends. One thread, no preemption — concurrency, not parallelism; promises are eager and not cancellable (that’s AbortSignal’s job).
- Functional idioms — currying, IIFE, tagged templates, generators, eval
Functions are values that close over their scope, so these idioms are closures with a shape: currying turns f(a,b,c) into f(a)(b)(c); partial application fixes some arguments now (bind); an IIFE makes a private scope and runs it once; a tagged template is a call on a template’s pieces; a generator is a pausable function that yields values on demand.
- Prototypes, objects and classes
Every object has a hidden [[Prototype]] link; a read that misses own properties walks the chain to null — objects delegate at run time, nothing is copied. class is mostly syntax over constructor functions and their .prototype.
- Scope, closures and this
Scope is lexical — fixed by where code is written: every function keeps a hidden [[Environment]] pointer to the environment it was created in, and a closure is that pointer keeping variables (not values) alive. this is the opposite — dynamic, decided by how the function is called (except arrows).
- Types, coercion and modules
Seven primitives (immutable, by value) + object (by reference). Coercion runs a few spec algorithms — ToPrimitive, ToNumber, IsLooselyEqual — so every “WAT” is traceable. ESM imports are live bindings; require returns an object.
networking
- GraphQL on iOS — Apollo iOS 2.x
GraphQL is a typed query language over one endpoint: the server publishes a schema, the client sends an operation naming exactly the fields it wants (+ typed variables), and gets back JSON of the same shape as {data, errors}. Apollo iOS generates Swift types from your .graphql files and keeps a normalised cache: every object with a cache ID is stored once, so every query that contains it sees the same, updated value.
- HTTP/1.1 · HTTP/2 · HTTP/3 — and the headers that matter
Same semantics (methods, status, headers — RFC 9110) on three wire formats: 1.1 = text, one request at a time per TCP connection; 2 = binary frames, many streams multiplexed on one TCP connection (TCP head-of-line blocking remains); 3 = the same over QUIC/UDP with TLS 1.3 built in — per-stream loss recovery, fast setup, connection migration.
- OAuth 2.0 · OIDC · JWT — for a native app
OAuth 2.0 (RFC 6749) is delegated authorization: the app gets a scoped access token without ever seeing the password. OIDC adds authentication: an ID token saying who logged in. A native app is a public client (no secret), so it uses authorization code + PKCE (RFC 7636) in the system browser (RFC 8252). A JWT (RFC 7519) is signed, not encrypted.
- TLS 1.3 & PKI — handshake, trust, pinning
TLS gives confidentiality (AEAD), integrity and server authentication: an ephemeral ECDHE exchange makes the keys (forward secrecy), and a certificate chain ending in a root the device already trusts proves the key belongs to the hostname. TLS 1.3 (RFC 8446) does it in 1 RTT; resumption can send data in 0 RTT — replayable.
- WebSockets & realtime on iOS
A WebSocket (RFC 6455) is an HTTP/1.1 request that upgrades (101) into a full-duplex stream of small frames on the same TCP connection. It gives foreground realtime; it does not give delivery across reconnects, ordering across connections, or life in the background — you build heartbeats, resubscribe, gap fill and dedup, and use APNs when the app is not running.
react-native
- Native modules — TurboModules, Fabric, Expo Modules
A typed TypeScript spec is the contract; Codegen turns it into C++/ObjC++/Java glue; at runtime JS holds a JSI host object and calls straight into C++ and on to Swift/Kotlin — no JSON bridge, sync possible, modules lazy-loaded.
- React hooks — the mechanisms
Hooks are slots on the fiber, matched by call order. Each render is a snapshot its closures capture. Effects sync with the outside world after commit; memo hooks keep identity. Most bugs: an old snapshot, or a dep with a new identity every render.
- React Native — the senior’s cheat sheet
A senior answers in trade-offs: RN = one React/TS codebase driving real native views, native code where the OS or the frame budget demands it — and a big app kept modular, observable, secure, accessible.
- React Native architecture — Bridge vs New Architecture
React runs on a JS thread; real native views live on the UI thread. The legacy Bridge joined them with async, batched JSON; the New Architecture (default since RN 0.76) uses JSI — JS calls C++ directly, sync or async — with the Fabric renderer and lazy, typed TurboModules generated by Codegen.
- React Navigation — navigators, nesting, deep links
Navigation is a tree of navigators holding one serialisable state object; actions (navigate, goBack) bubble up to the nearest navigator that can handle them, deep links are parsed down into that state, and screens stay mounted under a push — so “appear” is focus, not mount.
- Rendering & reconciliation — React on React Native
A render calls your components to produce a new element tree; reconciliation diffs it against the current fiber tree by type + key; only the differences are committed — on RN as a new Fabric shadow tree that is laid out (Yoga) and mounted as native-view mutations. Re-render ≠ native update.
- RN animations & gestures — Animated, Reanimated, RNGH
Smooth animation means computing each frame on the UI thread, so a busy JS thread cannot drop it. Animated’s useNativeDriver does this for a pre-declared animation of transform/opacity; Reanimated runs your own worklets on the UI thread; Gesture Handler recognises touches natively and feeds them. iOS analogue: Core Animation keeps running while your code blocks.
- RN performance — two budgets, lists, re-renders, startup
An RN app drops frames on two independent threads: the JS thread (React render, business logic, JS-driven animation) and the UI/main thread (native layout commit, drawing, native animation). Each gets 16.7 ms per frame at 60 Hz (8.3 ms at 120 Hz ProMotion). Senior answer: which thread is late, proved with a profiler on a release build, then the cheapest fix.
- RN state & data — the taxonomy, and the tool for each kind
“Which state library?” is the wrong question. Classify first — server, navigation, local UI, shared client, form, persisted (secret?) — each has a best-fit tool; most bugs are one kind kept in another’s tool.
- RN testing + TypeScript — the senior’s view
Buy confidence per second: TS strict for free, Jest for logic, the bulk in RNTL component tests that query like a user against mocked network, and a few E2E journeys — Detox (grey-box) or Maestro (black-box).
- Tooling & release — Expo, EAS, OTA, Metro, native builds
An RN app is a native binary (reviewed, signed, store-shipped) that runs a JS bundle made by Metro. Change native code ⇒ new store build; change only JS/assets ⇒ an OTA update may replace the bundle — but only for binaries with the same runtimeVersion.
recall
- Recall drill — the named facts you missed
Cover the blue, say the answer aloud, uncover. Every line is a question you answered “I don’t know”, half-right, or backwards. ! = missed twice / headline gap. Exact names, numbers, lists — the reasoning you already have.
runtime
- JS engines — JIT tiers, shapes, inline caches, GC
An engine parses to bytecode, interprets it while collecting type feedback, and JIT-compiles hot code in tiers that speculate on the shapes it saw — a failed guard deoptimises back to the interpreter. Memory is a tracing, generational GC: reachability, not reference counts.
- Node.js runtime — libuv, streams, scaling, shutdown
Node = V8 (runs JS) + libuv (event loop, OS async I/O, a 4-thread pool) + C++ bindings. Network I/O needs no threads (epoll / kqueue readiness); fs, dns.lookup, crypto and zlib go to the pool. Your JS stays on one thread — block it and every request waits.
- The JavaScript event loop — browser and Node
One thread runs JS to completion; when the call stack empties the host takes one task (macrotask), then drains the whole microtask queue, then (browser) maybe renders. Node’s loop is libuv’s phases, with process.nextTick and then microtasks drained after every callback.
security-build
- App hardening & privacy
The device is the attacker’s: everything in the .ipa is readable and every client check is hookable — so secrets and real decisions live on the server, and the client only proves things (TLS, App Attest). Privacy is the mirror image: you get one system prompt per permission, and must declare what you touch (purpose strings, privacy manifest, nutrition label).
- Code signing & iOS CI/CD
A build runs on a device only if it is signed with a certificate’s private key and carries a provisioning profile that lists that certificate, the App ID, the entitlements and (dev/ad hoc) the device. CI breaks because the runner has none of that — fastlane match + an App Store Connect API key make signing and upload reproducible and non-interactive.
- Crashes & symbolication
A crash report says what killed the process (Exception Type + Termination Reason), where (the crashed thread’s backtrace of image + offset), and which build (the UUID of every binary image). Symbolication turns offsets into function + file:line using the dSYM whose UUID matches — no matching dSYM, no names, ever.
- Keychain · Secure Enclave · biometrics
The Keychain is an encrypted database outside your sandbox (securityd); its rows open with class keys that depend on lock state. The Secure Enclave is a separate chip: keys born there never leave — data in, signature out. Biometrics are secure only when they gate a key, never when they return a Bool.
- Linking & launch time
Static code is copied into your binary at link time (no load cost, but one copy per binary); dynamic code stays a separate Mach-O that dyld maps, fixes up and initialises at every launch. Launch = pre-main (dyld + runtime init) + post-main (UIKit + your didFinishLaunching → first frame); aim for first frame in ≤ 400 ms.
- Modularization & Swift Package Manager
Cut the app into modules (local SPM packages) along seams that change independently; features depend on each other’s interfaces only, dependencies point down, and a thin app target composes the concrete implementations. The payoff is faster incremental builds and compiler-enforced boundaries.
swift
- Actors · reentrancy · isolation — and why not just @MainActor?
An actor is a reference type whose mutable state is guarded by a serial executor: one task at a time runs its isolated code, outsiders await. It is reentrant — at every await inside it other calls may run, so a check made before a suspension can be stale after. @MainActor is one global actor on the main thread: everything there is serialised with the UI.
- AsyncSequence & AsyncStream
An AsyncSequence is a pull-based sequence whose iterator’s next() is async: for try await suspends until the next element or nil (end). AsyncStream turns a push source (delegate, callback, notification) into one: the producer yields into a continuation, values wait in a buffer (default unbounded, no backpressure), one consumer task pulls them, and onTermination tears the source down.
- Closures in depth — capture, escaping, autoclosure, Sendable
A closure is a function + a context holding what it captured, and it is a reference type. By default it captures variables, not values (a captured var moves into a shared heap box); a capture list copies values when the closure is created. Parameters are non-escaping by default — @escaping says the closure may outlive the call, and that is what brings heap contexts, ARC and cycles.
- Codable in depth — containers, polymorphism, strategies, errors
Codable = Encodable & Decodable. The compiler synthesises CodingKeys + init(from:) + encode(to:) from the stored properties; you write them by hand when the JSON’s shape differs from the type’s. A Decoder hands out containers (keyed · unkeyed · single-value) and every failure is a DecodingError whose codingPath says where.
- Enums & pattern matching — sum types + the 8 patterns
An enum value is exactly one of its cases (a sum type / tagged union); raw values are one compile-time constant per case, associated values are per-instance payloads. switch must be exhaustive, patterns bind and filter, and for library enums @frozen vs @unknown default decides who may add a case.
- Equatable · Hashable · Comparable — the contracts
Equatable = value equality (==); Hashable adds hash(into:) under one rule — a == b ⇒ equal hashes (never the converse); Comparable adds a strict total order from only <. Set/Dictionary find a key by hash first, then == — break the contract or mutate a key and elements vanish.
- Error handling — throws, Result, typed throws, async
Swift errors are values (any type conforming to the empty protocol Error) returned on a separate, checked path: a function says throws, every call site says try, and the error travels up until a do/catch handles it. No stack unwinding across unmarked frames, no cost on the success path, no stack trace.
- Initialization — two phases, three delegation rules
An initializer must give every stored property a value before the instance is used. Structs get a free memberwise init; classes split init into designated (initialise own properties, delegate up to super) and convenience (delegate across to self.init), and run two-phase initialization: phase 1 fills memory bottom-up, phase 2 customises top-down.
- Key paths & access control
A key path (\User.name) is a typed, reusable, uninvoked reference to a property — a class instance you apply with x[keyPath: kp]; its class (read-only vs writable) is decided by what you may do with the property. Access control is lexical: six levels, default internal, and nothing may be exposed through something less visible than itself.
- Macros & result builders — compile-time code generation
A macro (Swift 5.9) is a compiler plugin that receives the syntax tree of its use site and returns new syntax, spliced in at compile time — additive only, no runtime cost. A result builder (@resultBuilder, Swift 5.4) rewrites the statements of a closure into nested static build… calls that fold them into one value — the engine behind SwiftUI’s @ViewBuilder.
- Numerics — overflow, conversions, floating point, money
Swift integer arithmetic traps on overflow (in Debug and Release) — wrapping is opt-in with &+ &- &*; every lossy conversion has a named init that says how it loses. Double is binary IEEE 754, so decimal fractions are approximations: compare with a tolerance, and keep money in Decimal (built from strings) or integer minor units.
- Objective-C essentials + Swift ↔ ObjC interop
An ObjC call is a message: [obj sel] becomes objc_msgSend(obj, @selector(sel)), resolved at runtime in the receiver’s class — hence nil messaging, categories, KVO, swizzling. Swift enters that world via @objc (visible to the runtime) and dynamic (dispatched through it), on NSObject-rooted classes.
- Operators, subscripts, literals, callAsFunction, :=~=
Operators are ordinary (usually static) functions with special syntax; a precedence group decides how an unparenthesised expression parses. Subscripts are property-like members called with [ ]. A literal has no type until context picks an ExpressibleBy…Literal type. :=~= is what switch calls per pattern.
- Optionals — an enum, not a null pointer
T? is sugar for the stdlib enum Optional<Wrapped> { case none; case some(Wrapped) }; nil is .none via ExpressibleByNilLiteral. Absence is part of the type, so the compiler forces you to unwrap — binding, chaining, ??, map, pattern matching — and ! is an explicit bet that traps on nil.
- Ownership · exclusivity · memory layout
Exclusivity is the rule (no overlapping access where one is a write); ownership (borrowing/consuming/ ~Copyable) is the vocabulary for who owns a value and when it dies; layout (MemoryLayout, existentials, refcount headers) is what all of it costs in bytes and atomic ops.
- Parameter packs & the new syntax (5.9 → 6.4)
A parameter pack (5.9) lets one generic declaration take any number of type parameters: <each T> declares the pack, repeat pattern expands the pattern once per element, and each x names the current element — no more 1…10 overloads. Around it, every release since added small syntax that deletes boilerplate: expression if/switch, trailing commas, raw identifiers, weak let, module selectors, some P?, @diagnose, await in defer.
- Property wrappers — what the compiler writes for you
@propertyWrapper (SE-0258, Swift 5.1) is a type with a wrappedValue; annotating @W var x makes the compiler store a hidden _x: W and turn x into a computed property forwarding to _x.wrappedValue — plus $x for projectedValue. SwiftUI’s @State, @Binding, @AppStorage and Combine’s @Published are just such types.
- Strings & collections — Unicode, storage, complexity
A Swift String is a UTF-8 buffer (since Swift 5) seen as a bidirectional collection of Characters, where a Character is an extended grapheme cluster — one user-perceived character of variable byte width. Hence no s[3], count is O(n), and slices (Substring, ArraySlice) share and pin their parent’s storage. All stdlib collections are value types with copy-on-write.
- Swift ↔ C / C++ interop
Swift reads C headers through its built-in Clang importer: a header exposed as a module (module map; or a bridging header in an app target) becomes global Swift functions and types — C scalars map to CInt/CChar…, pointers to Unsafe*Pointer. C++ (Swift 5.9+, opt-in) imports copyable classes as value types. The other way, @c (Swift 6.3; before it the unofficial @_cdecl) exports a Swift function under a C symbol.
- Swift 6 · strict concurrency & data-race safety
In the Swift 6 language mode data-race safety is a compile-time guarantee: all mutable state belongs to one isolation domain (an actor, a global actor like @MainActor, or nonisolated); a value crosses domains only if it is Sendable or provably never used again by the sender (region isolation / sending). Swift 5 warnings become errors.
- Swift compiler pipeline + runtime internals
The frontend parses, type-checks (a constraint solver), lowers to SIL — Swift’s own IR that still sees generics, ownership and ARC — runs mandatory then performance passes, then emits LLVM IR. At run time every type has metadata; generic and protocol code goes through value/protocol witness tables unless the optimiser could specialise it, which needs the body visible.
- Swift Regex — literal, runtime, RegexBuilder
Regex<Output> (Swift 5.7, iOS 16): Output is the typed shape of a match, (Substring, captures…). Three builds: literal (checked + typed at compile time), Regex(string) (runtime, throws, AnyRegexOutput), RegexBuilder (typed, composable, embeds Foundation parsers). Matches Characters, returns String.Index ranges.
- Time in Swift — Clock, Duration, timers
Clock (Swift 5.7, iOS 16) abstracts a time source: now, minimumResolution, sleep(until:tolerance:). ContinuousClock keeps counting while the system sleeps, SuspendingClock stops; both are monotonic, unlike Date, which is wall-clock and can jump. Measure elapsed time with a clock, store absolute moments as Date, and inject the clock so tests never really wait.
- Unsafe pointers · Unmanaged · metatypes · Mirror · casts
Unsafe = you, not the compiler, own lifetime, initialisation, alignment, type binding and bounds. Reflection is deliberately thin: metatypes are values (T.Type), Mirror only reads stored properties, casts query runtime metadata.
swiftui
- Animations — SwiftUI & UIKit
You never animate frame by frame: you change state (SwiftUI) or set the final value (UIKit) inside an animation context, and the framework interpolates every animatable property old → new. The questions are always: which change is in the context, and can it be interpolated?
- Rendering pipeline — layers, frames, hitches
Your app never draws the screen: it edits a layer tree and commits it once per frame to the out-of-process render server, which renders it on the GPU for the next vsync. Every frame has a deadline — 16.67 ms at 60 Hz, 8.33 ms at 120 Hz (ProMotion). Miss it in your process → commit hitch; miss it in the render server → render hitch. Either way the old frame stays on screen.
- SwiftUI ↔ UIKit interop
A representable struct wraps a long-lived UIKit object: make runs once per identity, update on every change, a Coordinator carries callbacks back. The other way, UIHostingController is a real view controller showing a SwiftUI view.
- SwiftUI Environment & Preferences
Two implicit channels through the view tree: the environment flows down (an ancestor sets a value, every descendant can read it), preferences flow up (descendants emit values, SwiftUI reduces them, an ancestor reads the result). Explicit init parameters remain the default for a view’s own data.
- SwiftUI Gestures & Canvas drawing
Gestures are value types composed with combinators (replacing recognizer-delegate arbitration); @GestureState resets on end and cancel. Shape = one vector view, Canvas = one immediate-mode view for thousands of primitives, drawingGroup() = flatten a subtree into one Metal-rendered offscreen image.
- SwiftUI layout system
Layout is a negotiation, not a constraint solver: the parent proposes a size, the child chooses its own size (it may ignore the offer), the parent places the child. Every modifier is a new parent in that chain — so order matters.
- SwiftUI navigation
Since iOS 16 navigation is data: a NavigationStack renders a path (an array of Hashable values) that you own; navigationDestination(for:) maps each value type to a screen. Push = append, pop = remove, deep link = assign a whole path.
- SwiftUI performance & identity
SwiftUI keeps state and view lifetime per identity, and re-runs body only for views whose dependencies changed. Fast SwiftUI = stable identity (state survives, diff is cheap) + narrow dependencies (few bodies run) + cheap bodies (each run is fast).
- Transferable, drag & drop, sharing, pickers
Transferable (iOS 16) describes once, as an ordered list of representations keyed by UTType, how a value leaves and enters the app; ShareLink, .draggable, .dropDestination, PasteButton and PhotosPicker all consume it — replacing separate UIActivityItemSource, NSItemProvider and UIPasteboard code.
testing
- Async & network testing — expectations, MockURLProtocol, clocks
Wait for the event, never for time. async code: make the test async throws and await. Callbacks: XCTestExpectation + wait(for:timeout:). Network: run the real URLSession with a MockURLProtocol on an .ephemeral config, and cover all three failure paths. Time: inject a clock/scheduler and advance it — no sleep.
- Flaky tests · CI · coverage · TDD
A flaky test has a nondeterminism source — shared state, order, real time, real network, animations, concurrency, environment — so remove the source, never retry. Coverage says what ran, not what was verified. TDD = Red → Green → Refactor in minute-long cycles; the failing test comes first.
- Test doubles — Dummy · Stub · Spy · Mock · Fake
A test double (Gerard Meszaros, xUnit Test Patterns, 2007) stands in for a DOC so the SUT can be tested alone. Stub/Fake control indirect inputs → state verification; Spy/Mock observe indirect outputs → behaviour verification; a Dummy just fills a parameter. The real distinction is direction and who verifies.
- Testable design — DI, seams & legacy code
A seam is “a place where you can alter behaviour in your program without editing in that place” (Michael Feathers, Working Effectively with Legacy Code, 2004); its enabling point is where you choose the behaviour. In Swift the seam is a protocol and the enabling point is the init — code that reaches for .shared, Date() or UserDefaults.standard has none.
- Testing fundamentals — the pyramid, FIRST, AAA
Many fast unit tests (∼70 %), fewer integration tests (∼20 %), very few UI/E2E tests (∼10 %). Going up buys realism and costs speed, money, stability and failure localisation — so the shape slopes. Tests are an executable spec whose real payoff is the confidence to change code.
- Testing ViewModels, Coordinators, UI & snapshots
Put logic in a ViewModel with protocol-injected dependencies and unit-test its state transitions (fast, in-process); test a Coordinator’s routing decision, not UIKit; keep XCUITest (separate process, accessibility tree) for a few critical journeys; pin snapshots to one device/OS/locale/appearance.
- XCTest & Swift Testing — lifecycle, asserts, leak test
XCTest makes a fresh XCTestCase instance per test method and wraps each one in setUp → test → tearDown; Swift Testing (import Testing, Xcode 16+) does the same with a @Suite’s init/deinit, @Test and #expect/#require. The leak test = weak ref → drop the strong → assert nil.
typescript
- TypeScript generics & type-level programming
Type parameters are solved by inference at each use, bounded by extends; conditional, mapped and template-literal types form a small pure functional language the checker evaluates — erased at runtime, limited by instantiation depth, and only as good as the inference it enables at call sites.
- TypeScript in practice — tsconfig, modules, boundaries
Types are erased: the compiler proves consistency inside the program, so the senior’s job is the edges — a strict tsconfig, module settings that match whoever actually loads the code, runtime validation where data enters, and a build that stays fast as the codebase grows.
- TypeScript type system — how the checker reasons
A type is a set of values; assignability is subset, judged by shape (structural), not by name. Unions must be narrowed along control flow before use — and it is all erased at runtime. Soundness is traded for ergonomics in known holes: any, as, method bivariance, readonly props.