FitTrack — Interview Talking Points
The 30-second pitch
“FitTrack is a workout-logging app on iPhone + Watch that pulls samples from HealthKit, persists workout metadata in SwiftData with CloudKit sync, and renders trend dashboards in Swift Charts. The Watch app runs an HKWorkoutSession with Live Activity + Dynamic Island updates on the iPhone. The interesting design was wrapping HealthKit’s awkward observer-query boilerplate behind an AsyncSequence — the consumer writes
for try await samples in stream.samples()instead of implementing two query types and routing between them.”
The 3-minute deep dive
If asked “tell me about the HealthKit query layer”:
“HealthKit’s observer pattern is one of the rougher Apple APIs. To keep a view current with new heart rate samples, you need
HKObserverQueryto learn that something changed, thenHKAnchoredObjectQueryto fetch the delta since your last seen anchor, then merge that into your local state. It’s about 200 lines per sample type if you write it idiomatically, plus you have to manage the anchor’s persistence across app launches yourself.I wrapped all of that in a
HealthQueryStreamactor that exposes new samples as anAsyncThrowingStream. Behind the scenes it owns the observer query and the anchored query; on the consumer side you writefor try await samples in stream.samples() { ... }. The anchor lives in actor state and persists to UserDefaults on shutdown.This collapses three view models to one and makes the consumer testable — a fake stream is just an
AsyncStreamthat yields canned samples on a schedule. We use it for heart rate, steps, energy, distance. The same actor type, four instances, four lines of view code each.The tradeoff: a single actor hop per delivery, which adds about 50 microseconds. For HealthKit samples that arrive every few seconds at fastest, that’s nothing. If we needed to consume samples at 60 Hz from a hypothetical CoreMotion sensor, I’d reconsider — but for HealthKit it’s the right tool.“
12 interview questions
1. “Why SwiftData and not just storing everything in HealthKit?”
HealthKit can store HKWorkout but not the user’s notes, tags, or attached photo. We need a place to attach FitTrack-specific metadata to each workout. SwiftData is the lightest local store that also gives us CloudKit sync for free. The HKWorkout’s UUID is the join key — both records carry the same UUID, so we can correlate them.
2. “Why not just SwiftData and not HealthKit?”
Then the user’s workouts wouldn’t appear in Apple Health, wouldn’t contribute to the move ring, wouldn’t sync to their other Apple-Health-aware apps. HealthKit is the platform-standard place; we have to write there. Plus, Apple Watch heart rate samples arrive through HealthKit — there’s no other API. So we’re already integrated with HealthKit for reads; writing the workout to it is one more line.
3. “What happens when the user denies HealthKit?”
Graceful fallback to manual entry. The Workout model has all the fields a HealthKit-denied user needs; we just don’t write to HKHealthStore. The Watch app, which depends on live HR, shows a “HealthKit required” message. The iPhone app remains fully functional. Critically: I never re-prompt — Apple’s rule is that HealthKit auth requests are one-shot per type, and re-asking is bad UX.
4. “How do you handle SwiftData + CloudKit schema migrations?”
This is the rough edge of the current SwiftData. For additive changes (new optional property), CloudKit handles it. For breaking changes (renaming a field, changing a type), I plan a v2 model alongside the v1, migrate on first launch after the upgrade, then drop v1 in v3. I document this in the README so future-me remembers the process.
5. “Walk me through starting a workout on Watch.”
User taps an activity type. We create an HKWorkoutSession + HKLiveWorkoutBuilder configured for that activity. Session starts; builder begins collection. The delegate fires for each new sample (heart rate, active energy) and we update our @Published properties. The view stays alive while the session runs. On end, we call session.end(), builder.endCollection(at:), builder.finishWorkout() — which produces the persisted HKWorkout. Then we create the matching SwiftData Workout with the same UUID.
6. “How does the Live Activity get updated?”
The Watch publishes session state via HKWorkoutSessionMirroredObject (iOS 17+) to the iPhone-side workout. The iPhone observes that, and every 5 seconds calls activity.update(state) with the latest heart rate and elapsed time. Apple throttles Live Activity updates internally; 5-second cadence is comfortably within their limits. When the workout ends, we call activity.end(...) to dismiss the Dynamic Island.
7. “How would you make the charts feel snappier on a phone with 5 years of workouts?”
Two layers. First, aggregate. Don’t chart raw samples; chart pre-computed daily averages stored in a separate SwiftData entity. Compute on save, not on read. Second, paginate the time window. Show 30 days by default; on user request, load 90 or 365 with a brief loading state. Third, render via Swift Charts with a fixed Y-domain so axes don’t recalculate. With those, even 5 years of dense HR data charts in under 200 ms.
8. “How do you test the HealthKit code?”
HealthQueryStream accepts a HealthStoreProtocol instead of HKHealthStore directly. Tests inject a fake that yields pre-canned samples on demand. The high-value test asserts the anchor advances correctly across multiple deliveries — that’s the bug that’s caused half the HealthKit-related issues I’ve ever debugged. UI tests run on a simulator with HealthKit denied; we verify the fallback paths.
9. “Tell me about a bug.”
Live Activity wouldn’t update for users who started a workout, locked the phone, then unlocked an hour later. Took two days. Turned out the Activity.update call was happening but the system was throttling because we hadn’t requested the background runtime extension correctly. Fix: set the Activity staleDate correctly so iOS knows when to refresh, and ensure the iPhone-side activity controller uses Task.detached rather than relying on UI-tied lifecycle. Lesson: Live Activities require explicit staleness contracts, not implicit lifecycle.
10. “Why no GPS / route map?”
Scope. GPS adds CoreLocation always-on permission, route storage, MapKit polyline rendering, watch battery impact, and Apple Review scrutiny on background location. Each is a multi-day investment for a feature that’s tangential to “log a workout.” For a capstone, the line is “tracked-route apps are a different product”; for a real product, I’d ship without it and add later if users asked.
11. “What about Apple Health’s deletion model?”
Important nuance. When the user deletes data inside FitTrack, I delete only my SwiftData records and the matching HKWorkout I wrote. I do NOT delete samples (heart rate, etc.) — Apple explicitly forbids third-party apps from deleting user health samples; the user must do that themselves in Apple Health. The FitTrack delete sheet states this clearly so users aren’t surprised when their Apple Health view still shows the underlying data.
12. “How would you scale this to 1 million users?”
CloudKit private DB scales automatically per user. HealthKit is local-only — no server cost. The only scaling work is making the dashboard fast for users with years of data (covered in question 7) and making the complication/widget extensions stay under their memory limit. For 1 M users I’d also add a thin crash-reporting integration (Sentry, but with PII scrubbing) so I can spot regressions. No backend service required.
Red-flag answers to avoid
If asked “did you ship it to the App Store,” don’t lie. TestFlight is enough for a capstone. Answer: “TestFlight, with five external testers. Full App Store submission would require a few more days of polish on screenshots and a privacy policy review — the build is ready, I deprioritized that step.”
If asked “why no machine learning,” don’t say “I didn’t get to it.” Say: “Out of scope. HealthKit + Charts + Live Activities was a coherent product story. Adding ML — say, predicting tomorrow’s energy from past samples — is a separate concern with its own model selection and accuracy story. I’d build it in a separate phase.”
Next: Capstone 3 — ShopKit.