On Doing The Dumbest Thing First

Or how experience in embedded development helped me get a backend development job

As is pretty standard in the software engineering interview process now, a few jobs ago I was given a take home technical test.

Trying to be sufficiently vague so as not to spoil that test for future interviews, the test basically involved receiving data via TCP over a socket connection. Above that everything else was custom to the system at the other end of the socket. A custom “protocol” was defined to specify a “packet” layout, pretty standard stuff, like a byte for the size of the “packet”, a data section, etc… Obviously the point of the techical test was to write a “client” to receive & interpret the data being sent over that socket.

I’ve used “packet” in quotation marks in this note to refer to the application’s “packet” concept, and not a packet (no quotation marks) in TCP/IP world. In most cases in this note we’ll be talking about the application “packet” rather than TCP/IP ones, I’ll try to be explicit when we the latter come back up.

Apparently most candidates treat the problem like this

while true {
    read everything received on the socket
    validate/parse/etc... as a single "packet"
    process the "packet"
}

However, I think there is a “dumber” way to do this.

Here’s how I typically work with UART/serial connections in embedded environments

while true {
    read next received byte into buffer
    validate/parse/etc... contents of buffer
    process the "packet" if buffer contains one
}

In the embedded world, especially for hobby projects, hobby-grade equipment, or older accessory devices, sometimes we need to work with low transmission speeds. This could be 9600 baud or lower, so under 10000 bits/second. The equates to something like 1000 bytes/second or fewer, or 1 byte per millisecond. Even modern hobbyist grade microcontrollers like Arduino operate in teens to 20 MHz, which comes out to a clock cycle each 50 nanoseconds. Most operations on these processors I think take a clock cycle, which means that something like 20000 operations can happen for each byte received. Consequently we should generally not assume that a complete message is available at any one time, we should expect to be “drip-fed” bytes of the message, so it makes sense to read them out one-by-one into an internal buffer.

We also in the embedded world often have a very small hardware buffer available for things like UART. Think of a hardware buffer as a set of registers where incoming UART bytes are stored before they are read out into memory for the application code to process. Buffer sizes of a few bytes are not uncommon, though there are certain even low cost microcontrollers which have teens or even tens of bytes in the buffer. When we are working with a device whose hardware buffer size is small or we are assuming it is small, generally we are forced to move those bytes out of the hardware buffer into a software buffer quite quickly.

In any case, I didn’t see why this read in byte-by-byte approach shouldn’t or couldn’t also be the case for bytes on a TCP connection too. Modern processors are quite fast, certainly one tends to assume that processor-y things are going to happen on a much shorter timescale than network-y things.

Plus it’s the more grug brain approach, right? Grug receive byte. Grug move byte to buffer. Happy grug.

The “reading everything from the socket” approach is making the assumption that each “packet” of data arrives in one go and potentially also that at most one “packet” of data arrives at once. Apparently this can go wrong when the rate at which the server is sending out data (“packets”) is high, and it can happen that multiple application “packets”, even when the server sends them to the network over multiple network calls, end up in the same TCP/IP packet. Probably it’s theoretically possible that a single “packet” sent to the network by the server gets split over multiple TCP/IP packets, though I suppose that would be quite rare unless the application “packets” are huge.

As far as I understand, everything in the TCP/IP packet is likely to become available to read from the socket pretty much at the same time, thus the client application code reading everything from the socket can get more than one (or theoretically fewer than one) application “packets” at once. If you have written your client code under the assumption that you’ll always get one whole “packet” when you read from the socket, now it’s probably going to start failing validations, dropping data, emitting error message, etc…

If instead you take the approach to read one byte from the socket at a time and keep checking whether you have a valid application packet in your software buffer, you won’t have these problems.

Consequently when my take home test solution was submitted and tested at high data rates, it didn’t explode and it didn’t drop any data. Apparently it’s quite rare that a candidate in those interviews achieves this!

Frankly, I am proud of this outcome, even though I kind of achieved it just by not being very smart. The point of this note though is not to boast, rather I want to advocate for two things.

Firstly, start out from the absolute dumbest thing you can do and adding cleverness from there where required. There are a bunch of objections you could make to the byte-by-byte approach that I took, mostly around performance. In an interview/take-home situation this is something to comment on, note down, highlight and discuss with your interviewers - why might there be performance concerns, how might you assess whether they apply to the real system, what strategies might you take to improve the performance (I’ll leave this as an exercise for the reader). When you actually get the job, just replace interviewers in that sentence with colleagues. For what it’s worth I’ve seen “do the dumbest thing first, add cleverness where required” work generally really well in most of the places I’ve worked and colleagues have been sympathetic to this approach.

Secondly, if you do like to code in your free time, don’t be afraid to try things which don’t give obvious immediate benefitto your career. In my experience the chances are good that you’ll accidentally learn something that proves valuable at a totally unexpected time in the future.