From embedded to cloud - why we work both ends of the stack

Imagine a relay race where each runner speaks a different language, none of them watched the others practice, and the baton is a number that quietly changes meaning every time it’s handed off. That’s what building a connected hardware product looks like from the inside. A temperature leaves a sensor as a raw voltage, becomes a 12-bit integer in firmware, gets averaged in the cloud, and lands on a screen as “23.4°C.” Four runners, four languages, one baton - and when the final number is wrong, every runner swears they ran their leg perfectly.
Most engineering teams pick one leg of that race and live there. Frontend shops. Backend shops. The occasional “full-stack” shop that, in practice, means TypeScript on both ends of a web app. We deliberately work a much wider range - from the C++ firmware on a microcontroller to the Svelte or React dashboard a human actually looks at - and this post is about why that width is a feature, not a vanity.
The worst bugs don’t live in a layer. They live in the seams.
Here’s a bug we’ve watched play out, in one variation or another, on nearly every connected-product project we’ve touched. Follow the baton.
A humidity sensor reads about 2% high when the air gets muggy - a known quirk buried on page 14 of its datasheet. The firmware engineer, trying to save battery, buffers eight readings and transmits their average once a minute. The backend receives that average and, to keep the database tidy, rounds it to one decimal place. The dashboard takes the latest value and renders it as a single dot on a smooth line chart. Each of those four decisions is individually reasonable. Together, they produce a chart that, on humid afternoons, reads a few percent high, lags reality by a minute, and hides the variance entirely.
Now the support ticket arrives: “the chart looks wrong.” Watch what happens next.
- Firmware engineer: “The value is correct on the wire. I can show you the packets.”
- Backend engineer: “We store exactly what we receive. Here’s the row.”
- Frontend engineer: “We render exactly what the API returns. Here’s the network tab.”
- Customer: “I believe all of you. It’s still wrong.”
Everyone is telling the truth. No single layer contains the bug. It’s an emergent property of four correct-in-isolation decisions stacked on top of each other - a sensor calibration quirk, a power-saving average, a rounding step, and a visualization choice that erased the smoothing. To even see it, someone has to hold the entire path in their head at once: datasheet errata, firmware buffering, network format, database precision, and chart semantics. That person is the one who fixes it in an afternoon instead of three teams trading Slack messages for a week.
That’s the entire thesis. In a connected product, your hardest bugs are not in the code anyone owns. They’re in the handoffs nobody owns. Width is how you get coverage of the handoffs.
What “going wide” actually looks like
This isn’t theoretical. A few shapes we’ve shipped:
A sensor → MQTT → Postgres → SvelteKit dashboard stack for an industrial monitoring product. We wrote the ESP32 firmware, the ingest service, the database schema, and the operator UI. Two engineers, fourteen weeks, in production. The reason it went fast wasn’t heroics - it was that when telemetry “looked off,” one person opened the firmware and the query and the chart component in the same sitting and found the seam in minutes.
A Bluetooth-LE peripheral → companion mobile app → REST API → Stripe pipeline - hardware-as-a-service for a consumer device. The hardest engineering wasn’t in any single layer. It was the seam between physical possession (you paired with the device over BLE) and account ownership (you’re the one paying for it). What happens when you sell the device? Reset it? Pair a second phone? Those questions live between the radio and the billing system, and no layer-specialist owns them.
A PLC → on-prem gateway → cloud sync → executive dashboard path that takes factory-floor data all the way to a CEO’s iPad. The gateway sits in a control cabinet with bad WiFi and worse power. Every layer above it has to be designed assuming that gateway will vanish for two days and then reappear with a backlog. The dashboard’s “live” view is a polite fiction the whole stack conspires to maintain.
In each case the interesting work was between the boxes, not inside them.
The skill is translation, not omniscience
Let’s be clear about what we are not claiming. We are not “experts in everything.” Nobody is, and anyone who says so is selling something. A specialist who has spent ten years on RF design will out-debug us on an antenna problem every single time, and we’ll happily hand them the soldering iron.
What working both ends actually buys you is translation fluency - enough competence at every layer to know which question to ask at each seam, and enough humility to call a specialist for the parts that decide the product. Concretely, the boundary questions sound like this:
At the sensor-to-firmware seam: what does the datasheet actually guarantee, versus what does the “typical” column suggest? Where’s the calibration curve, and who applies it?
At the firmware-to-network seam: what’s the real meaning of a transmitted value - is it instantaneous, an average, a max-since-last-send? What’s the timestamp’s source of truth, the device clock or the server’s receive time? (They disagree, always.)
At the network-to-database seam: what precision and units does the schema enforce, and does that quietly truncate something the firmware worked hard to measure?
At the database-to-UI seam: is the chart showing raw points, a rolling average, or a downsample? Does the visualization imply a smoothness the data doesn’t have?
You don’t need to be the world’s best at any one of those to ask all four in a row. And asking all four in a row is precisely what a layer-siloed org structurally cannot do, because each question lives in a different person’s head and a different team’s sprint.
A mental model you can steal
If you take one practical thing from this, make it a habit: trace one real value end to end before you trust the system. Pick a single temperature, a single heartbeat, a single dollar. Follow it from the physical phenomenon all the way to the rendered pixel, writing down what it is and what it means at every hop:
raw analog signal → ADC counts → calibrated engineering units → buffered/averaged sample → serialized packet → received row → rolled-up aggregate → charted point
At each arrow, ask the two questions that catch almost everything: did the value change? and did its meaning change? A value can survive every hop unchanged and still arrive as a lie, because somewhere its meaning shifted - “instantaneous reading” silently became “one-minute average” and nobody updated the axis label. Teams that run this trace once, on purpose, before launch find the humidity-chart class of bug while it’s cheap. Teams that don’t, find it in a support ticket.
You don’t strictly need one team that works the whole stack to do this. You need someone who will hold the full path in their head and refuses to accept “it’s correct on my layer” as the end of the conversation. On the products we help build, that someone is usually us - not because we’re the best at any single layer, but because we’re comfortable enough at all of them to keep following the baton until the number is finally, actually right.
If you’re shipping something that has to work across hardware and software, and you’ve ever heard four people each correctly explain why a bug isn’t theirs, we’d like to hear about it.