Skip to content


Dependencies in IoT: Tear Some Down, Build Others Up

This year at the virtual ElixirConf, I was thrilled to deliver the closing keynote, where I spoke about the complexities of bringing an IoT product to life and the tools and processes that make it easier.

Watch the talk below, and explore this post to enrich your experience with supplemental content.

“Hello world!”

Every software engineer who has set out to learn and comprehend a new, complex system is familiar with this simple phrase. Saying hello to the world is our way to start thinking about the journey ahead into an unknown landscape.

At first glance, telling our computer to output a phrase like “Hello world!” seems so simple. Getting our computer to show those words may be as easy as typing:

echo "Hello world!"
print('Hello world!')

or, in the case of Elixir, something like this:

current_process = self() # Spawn an Elixir process (not an operating system one!) spawn_link(fn -> send(current_process, {:msg, "hello world"}) end) # Block until the message is received receive do {:msg, contents} -> IO.puts(contents) end

Yet, when we dive a little bit deeper into what’s going on behind the scenes, we realize that to say, “Hello world!”, we must first invent the world. Not only does our language need to be interpreted by the CPU, but to even have words appear on the screen in the first place requires fonts, pixels, and so much more…

When we boil it down, to say hello to the world, we first need to turn on a single light. We need to light up the pixels and render text to a display. So much depends on something even as small as that single light. In a very real sense, turning on that light is our first “Hello world!” moment.

So, when we look at our stacks and start working to build technology on top of them, we’ll do well to remember that we’re standing upon the shoulders of giants. If they move, then we move with them. Though this brings the risk that we’ll fall off – our program will crash, or maybe our circuit will even burst into flames – though that’s not to say we’re wrong to depend on what’s been done before us.

Without dependencies, we’d constantly have to reinvent the world. However, taking on too many dependencies, especially unnecessary ones, sets us up for failure. That’s why I want to take some time to talk about how to pick them wisely, how to strip down our technical dependencies to their barest form, but also how to nurture the kinds of dependencies that lead to true innovation.

Tearing Down Technical Dependencies

Technology is impossible without dependencies. One software module depends upon another. An application depends on the firmware sitting below it to access resources, while that firmware likewise depends on the hardware that it’s interfacing with. And, especially in IoT, that hardware depends on how it interfaces with the physical world it’s a part of.


We may need them, but software dependencies bring a host of problems with them. First, they are a major source of bugs, which arise from often invisible incompatibilities. Second, when a dependency changes, we likewise need to update the tech that depends upon it, and this can leave us in a never-ending refresh cycle. And, of course, integrating diverse technologies poses a huge challenge, especially when they’ve been developed in silos and we wait until the end to put them together.

That’s why we need a tempered approach to dependency management. We need to pick our dependencies wisely.

For instance, Very uses Nerves as our IoT platform of choice because it gives our developers such granular control over their dependencies. While this embedded Linux can integrate dependencies from everything ranging from C to Python, one of Nerve’s defining features is its ability to only ship what we need.

Most Linux distributions ship the entire kernel by default alongside a swath of software that ranges from “almost always important” to “is this really necessary?” With Nerves, however, you’re in the driver’s seat. You get to choose what drivers to install, which bootloader to include, which network utilities to use, and much more.

We’re always looking for creative ways to cut down on dependencies. However, for the dependencies we do embrace, we keep a fundamental truth at the front of our minds.

Rebecca Grinter, PhD in Information and Computer Science, writes that,

“Software development is difficult in part due to the relationships that exist between software modules. These technical relationships create and reflect social relationships that exist between developers, managers, and organizations. The management of these relationships is critical to producing systems, and when they are ignored or misunderstood, the chances of producing working software decline.”

This insight into the correlation between technical dependencies and social dependencies is just as true now as when Dr. Grinter wrote it in her 1996 dissertation. The only difference, especially when it comes to IoT, is that we’re now simultaneously working across the entire tech stack, including device hardware, firmware, server backends, and user front-ends.

Building Team Dependencies 

team dependency

The result of this focus is Very’s culture of blending teams from across expertise domains. While we may be a fully remote company, that doesn’t stop us from constantly communicating, continually integrating, and perpetually iterating. We’re fully committed to the most important agile development principles as we interpret them:

  • Continuously deliver value to end-users.
  • Ensure features are production-ready before moving on to new features.
  • Test early and often.
  • Make a decision at the last responsible moment, and not before.

An especially crucial part of our IoT development process is agile hardware development. While many organizations use a waterfall methodology where they finish building hardware before starting on the firmware, which in turn must be finished before starting on the server backend—and so on—Very’s agile approach enables us to collaboratively and concurrently work on these different parts of the stack.

Take the example of designing custom circuitry. Our hardware and firmware engineers are all equipped with Voltera 3D printers for printing their own circuit boards. The hardware engineers can work together on a prototype by putting their hands on the same components, and once an iteration is complete, the firmware engineers can print the board and start writing code. If something doesn’t work, they can tell the hardware people, who will build the next prototype.

Then, once we have a minimum code base for interacting with the hardware, the server engineers can then begin simulating their target environment so that they can start working. As a result, the front-end developers now have a mockup server to interface with.

The real takeaway is that this methodology fosters genuine relationships that are built on trust. So, when the one type of developer trusts another domain’s engineers, they’re able to depend on each other to create a fully integrated system.

These dependencies, the ones that involve relying on our co-workers on a basic, human level, are the key to successful technical dependencies.


At the end of the day, we’re left with a blueprint for a reasonable IoT product. They are as follows:

  • Understand your dependencies.
  • Minimize dependency, both in hardware and software.
  • Mock/simulate target-specific dependencies.
  • Integrate early and often.

By building up interpersonal dependencies and stripping away technical dependencies, we’re able to create products that are robust, fault-tolerant, and feature-rich. Of course, we care about the software APIs that tie our programs together, but what’s even more important are the interpersonal APIs that unite us into a team.

And, as a result, we can finally say:

“Hello world!”

IoT insights delivered to your inbox