Three principles that produce speed, agility, and quality
The technology under a connected product turns over every few years. Chips, languages, frameworks, and now AI. How you decide what to build, what to borrow, and how to put it together changes far more slowly. These are the three principles I keep coming back to on real projects, what each one looks like in practice, and where I have watched each one go wrong, including on my own work.
Most delays trace back to a decision, not to the code
When a project slips, the story is usually about a hard technical problem. When I go looking, the delay almost always traces back to a decision made months earlier: a platform picked because it was familiar, a release process nobody automated, a build slow enough that people stopped running it. The code was fine. The frame around the code was the problem.
There is a belief, still common in embedded, that the way to ship a good product is to own and control the entire stack yourself. It made sense when the stack was small. For a small team today it is quietly fatal, because it turns capable engineers into full-time maintainers of plumbing that other people already maintain better. The mirror failure costs just as much: standing your whole product on someone else's platform, unable to swap a component, tied to their roadmap and pricing. Both ditches come from the same place: letting something other than your own judgment choose the foundation.
A framework is worth having because it pulls those decisions forward, to the point where they are still cheap to change. Three principles, each covered below.
Be intentional about the foundation you build on
Borrow the process, not just the tool.
Every connected product stands on a platform, whether anyone wrote it down or not. A compiler, an RTOS, a Linux distribution, a build system, a message transport, a database, dashboards, deploy tooling, a scheme for part numbers. The question is never whether you have a platform. The question is whether you chose it.
Platform thinking is the discipline of making that choice on purpose and then owning the result. Owning it does not mean writing it. Most of my platform is other people's work. It means I can say what each piece is, why it is there, and what I would do if it went away tomorrow.
When you take a package from Debian, you are not just taking a file. You are taking the maintainers, the code review, the CI, the security response, the packaging expertise, and the release engineering that produced it, all of which someone else already runs at a scale a small team cannot match. Reusing the code is the small part. Reusing the work is where the leverage is. Build the same component from source yourself and you quietly take that entire job back.
It is the difference between eating at a restaurant and running one. You do not hire the cook, negotiate with farmers, and haul the ingredients home. You buy the whole system standing behind the meal, and that system is precisely what makes it worth paying for.
Right-sizing matters as much for vendors as for technology. I host on Linode rather than AWS or GCP, because a small team gets plain Linux servers, pricing it can predict, and support that answers. The large clouds sell support too, and the tier that reaches a human costs more than my hosting does. What matters is whether a vendor treats a team your size as a customer worth having.
This is also the honest part: reuse is harder than it looks. Understanding what someone else built takes more effort than understanding what you built yourself. For a small team building a complex system, it is still the only path that scales.
- Write your platform down. One page, every layer, what you chose and why. If nobody on the team can write that page, nobody owns it.
- Prefer components with a visible process behind them: an active maintainer, real CI, a security contact, and a release cadence you can observe from the outside.
- Keep an exit for every piece. If you can name the replacement and roughly what switching would cost, you are borrowing. If you cannot, you are dependent.
- Stay close to upstream defaults until you have a specific reason to deviate. Every deviation becomes something you maintain forever.
- Track what you have: versions, parts, units, and locations. Reuse only pays off when you know what is actually in the field.
- Building everything from source to stay in control, then watching your best engineers spend their weeks maintaining plumbing that other people maintain better.
- The mirror failure: standing the whole product on one vendor's platform, with no way to swap a component and no leverage over their roadmap, pricing, or lifecycle.
- Choosing a technology because a large company uses it. Infrastructure built for a 200-engineer organization usually requires a 200-engineer organization.
- A platform that lives only in one person's head, which leaves the team stranded the week that person is out.
Make shipping fast, boring, and frequent
Shipping is a capability you keep in shape, not an event you survive.
Release first says: build the smallest thing that can travel all the way out the door, ship it, and then keep shipping. The first release is where you find out what your process actually costs. Learning that in week one is cheap. Learning it two weeks before the deadline is expensive.
Most of the value is in what the practice forces you to fix. To release anything you have to version it, automate the build, package and sign the artifacts, and answer the question of how units already in the field get updated. Teams defer all of it, because none of it is the product. It is, however, the part that decides whether the product can change after it ships.
Frequency changes behavior. Release once a quarter and every release carries a quarter of accumulated change, so each one is risky, so people batch even harder, and the interval stretches further. Release every week and each one is small enough to reason about, small enough to roll back, and boring enough that nobody schedules a meeting about it. Shipping behaves like a muscle. Use it weekly and it carries the load. Use it twice a year and it strains under a weight it should handle easily.
The other half is decoupling. Firmware, edge software, cloud services, and hardware each have their own natural lifecycle. Force them onto one version number and everything moves at the pace of the slowest piece. Let each release on its own schedule, with clear interfaces between them, and the whole system gets faster without anyone working harder.
- Ship end to end in the first week, even if the product does almost nothing yet. It is the cheapest release you will ever do, and it prices the rest of them.
- Automate everything between a commit and an installable artifact. Any step a human has to remember is the step that breaks a release.
- Version each part of the system separately and let each follow its own lifecycle instead of one train that everyone waits on.
- Build and exercise the field-update path before the first unit ships. Updating deployed devices is the hardest release problem, and it never gets easier by waiting.
- Put every release in front of a real user. Iteration only compounds when feedback comes back.
- Treating release as a phase at the end, so the first complete build happens under deadline pressure with everyone watching.
- A release procedure that lives in a document as a list of manual steps. It works right up until the person who wrote it is on vacation.
- One version number spanning firmware, software, and hardware, which couples every schedule to the slowest one.
- Long-lived branches that accumulate for months. The risk lives in the unverified assumptions inside them, which all come due on the same day.
Optimize the place where the work actually happens
Every minute the tools take is a minute the product does not get.
A developer's day is one loop repeated a few hundred times: change something, build it, run it, look at what happened. Every other part of the workflow exists to serve that loop. When it takes ten seconds, engineers try things. When it takes forty minutes, they stop trying things and start guessing, and the quality of the product follows the quality of the guesses.
So I treat the loop as the primary design constraint. Technology choices, build system, test setup, hardware access, documentation: each one gets judged first on what it does to the time between an idea and observable behavior on real hardware. A tool that is slightly worse on paper and much faster in the loop usually wins.
The cost of a slow loop is easy to underestimate, because the visible part is small. Five extra minutes per build across a team is an hour or two a week, which sounds survivable. The part you cannot see on a timesheet is the experiments nobody ran, the refactor nobody attempted, and the hypothesis nobody tested because checking it would have cost the afternoon.
AI has raised the stakes considerably. An agent runs the same loop, far faster, and amplifies whatever system it is dropped into. Give it fast feedback, real tests, and clear structure and it multiplies a small team. Give it a slow, undocumented loop and it produces code faster than anyone can review, understand, or maintain. Open tools and open file formats matter more every year for the same reason: an agent can read and write them directly, the same way a person can.
- Measure the loop. Time from a saved file to observable behavior on real hardware, tracked like any other project metric.
- One command from a fresh clone to a running system, tested on somebody else's machine, not just yours.
- Choose open, text-friendly file formats so Git, a reviewer, and an AI agent can all work with the same source of truth.
- Give agents the same entry points people use: the same commands, the same tests, the same documentation. Anything only a human can run becomes a gap in what AI can help with.
- Fix the ten-second annoyance that everyone hits daily before the elegant refactor that nobody will notice.
- Tooling chosen for the purchasing checklist and paid for every day by the people who have to use it.
- A build that only reproduces on one machine, which turns onboarding and CI into ongoing archaeology.
- Accepting slow feedback as a fact of embedded life. Much of it traces back to specific choices that can be revisited.
- Layering AI onto a loop nobody trusts, which mostly increases the volume of code you cannot verify.
Three legs, one stool
Platform thinking decides what you stand on. Release first keeps it moving. Developer experience sets how fast you can move within it. Remove any one and the other two lose most of their value.
A foundation nobody ships on
You choose well, document it, and then discover a year in that getting a change to a deployed device takes a week of manual work. The platform was sound; nothing could travel across it.
Shipping whatever accumulated
You ship weekly, from a foundation nobody chose on purpose. Every release goes out fast, and each one carries a little more of a dependency you cannot explain or replace.
The right system, at half speed
A good platform and a real release process, wrapped in a forty-minute loop. The team makes correct decisions slowly, and stops testing the ideas that would have been worth testing.
There is really nothing holding us back except our own ability to put it together.
The line I keep coming back to, after 25 years of building these systems.
I wrote this for my work. What did I miss?
These principles came out of real projects, and they keep changing as I build. If your experience points somewhere different, I would genuinely like to hear it. And if you would like help applying them to a product you are trying to get out the door, that is the conversation I enjoy most. You can see how the principles land in practice on my technology page.