~/notes
Quick thoughts, TILs, and observations from building mobile & backend systems.
Circuit breakers beat blind retries when the downstream is already on fire. Fail fast, shed load, recover clean. Half-open state is the part everyone forgets to implement properly — without it you just DDOS yourself on recovery.
Riverpod’s ref.listen is underrated for side-effects — stop cramming navigation and snackbars into build. Listen fires once per state change, not on every frame. Your widget tree will thank you.
Go’s errors.Join finally made aggregating failures readable. Wrap with %w, join at the boundary, unwrap with errors.Is at the handler. No more string concatenation that swallows stack context.
Server-driven UI pays off the moment product wants to A/B a flow without shipping a new build. The tax is a robust schema contract and a typed renderer on the client. Pay it once, win every sprint after.
Growing SDK test coverage from 4% to 76% taught me one thing: start with the event dispatcher, not the UI. Dispatchers are pure functions with clear contracts — easy to test, impossible to skip. UI tests are flaky; logic tests are gold.
gRPC shines on internal service communication — typed contracts, bi-directional streaming, and a fraction of the JSON overhead. REST still wins at the public boundary where you need browser support and human-readable payloads. Use both, know why.
Kotlin Channel vs SharedFlow — Channel is a queue with backpressure (good for work items), SharedFlow is a broadcast with replay (good for events). Mixing them up causes dropped UI events or unbounded queues. Know the difference before you pick.
Face liveness with ML Kit: blink + smile + head-turn challenges cut spoofing attempts by ~65% in our KYC flow. The real gain wasn’t the challenges themselves — it was randomising the sequence so recorded videos can’t replay. Spoofers hate randomness.
Kafka consumer group rebalances are the silent killer of throughput during deploys. Use session.timeout.ms and max.poll.interval.ms to tune how quickly the group detects a dead consumer vs how long a slow consumer gets before eviction. Default values are too conservative for most production loads.
Swift’s @MainActor is a compile-time guarantee that UI mutations stay on the main thread — something we used to enforce with DispatchQueue.main.async and hope. Actors eliminate the data races but they shift the discipline from runtime crashes to await boundaries. Trust the compiler, not your memory.