How a simple digital wellness app for Garmin became an exercise in squeezing every drop of performance out of Monkey C.
574 words (Approximately a 3 minute read)
Recently, I revisited Clockish, a watch face I built for Garmin devices. The premise is simple: it shows the approximate time in conversational English (e.g., “quarter past ten”) rather than precise digits. It’s part of my personal push for digital wellness—reducing the “datapoint fatigue” that comes from checking your wrist and getting lost in progress bars and notifications.
It looks simple, but running a text parser on a device with limited memory and a tiny battery is surprisingly wasteful if you’re not careful.
I spent the weekend optimizing the codebase, taking it from a functional prototype to a production-ready app. Here’s what happened.
In my first pass, I was a bit reckless with memory. Every time the screen updated (potentially once per second), I was recreating look-up arrays for spoken hours and minutes.
// The "Old" Way: Wasteful allocations every second
function onUpdate(dc) {
var hoursSpoken = [[0, "midnight"], [1, "one"], ...];
// ... 48 array element allocations occur here EVERY second
}
I moved these to class-level constants. Initialising them once at startup eliminated thousands of unnecessary allocations per hour, significantly reducing garbage collection pressure.
Clockish rounds time to 5-minute intervals. This means that for 299 out of every 300 seconds, the text on the screen doesn’t actually change.
I implemented a caching layer that checks this “rounded” time. If the minutes haven’t crossed a 5-minute threshold, the app returns early, skipping all the string manipulation and UI updates.
This allows the app to bypass expensive operations in 99.7% of all update cycles.
Garmin devices have a sophisticated “Sleep Mode” when you aren’t looking at the watch. I updated Clockish to be a “good citizen” of the hardware ecosystem by implementing sleep state tracking.
When the watch enters sleep mode, the app pauses almost all logic. When it wakes (via a wrist gesture), it forces an immediate refresh. This ensures the user sees the correct time instantly, but the CPU stays virtually idle while the watch is at your side.
As the rounding logic grew more complex (handling transitions like “23:35” becoming “midnight”), the code became littered with “magic numbers” like 35, 12, and 60.
I refactored these into named constants like MINUTES_THRESHOLD_NEXT_HOUR. It’s a small change that makes the codebase dramatically more maintainable. If I ever want to change the rounding interval to 10 minutes, I now change exactly one line of code instead of hunting through the entire project.
This groundwork paves the way for a feature coming soon: the ability for users to set their own level of accuracy.
Finally, I added defensive programming. In the constrained environment of a fitness watch, a single “Null Pointer” or “Array Out of Bounds” error can crash the entire watch face, leaving the user with a blank screen.
If a UI element is missing or a calculation goes rogue, the app degrades gracefully rather than crashing.
Spending a weekend optimizing a simple text display might seem like overkill. But for me, this is the essence of local-first engineering. Whether it’s a web app or a watch face, we should be building software that is:
Clockish is now faster, more stable, and significantly kinder to my Garmin’s battery. Version 1.0.0 is now live on the Garmin IQ Store.