Code Design Interviews

My favorite programming subject for a 45–60 minute interview is code design.

Working through design is a window into how someone works day to day. We can talk about the weaknesses and strengths of a multitude of decisions and all from a perspective of what’s best for the current known state of the world. I want to evaluate this because maximizing code readability and maintainability is a key trait of the best engineers.

So code style interviews! I use a problem I stole almost a decade ago after experiencing it first-hand.

Here’s the problem. You write software for tracking durability of a wooden chair. The solution is responsible for the following.

  1. When will it burn? Given a temperature how long can the chair be at that heat before it starts to be damaged.
  2. What will break it? Given a point on the chair (x,y,z coordinates) what weight can the chair handle before it’s damaged, will the chair be damaged?

I’ll work together with the candidate to get us to a starting code base like the following.

public class WoodenChair {
public double whenWillItBurn(double celsius) {
...
}

public double breakWeight(double x, double y, double z) {
...
}
}

Now I can introduce the incremental feature. We want to support wooden tables. The functionality for whenWillItBurn is exactly the same, but the functionality for breakWeight is different. The whenWillItBurn is defined as f(x,y,z) * woodConstant where f is different for tables andchairs.

This should trigger for most OO people an inheritance impulse. And often we’ll get to some version of

public abstract class Wood {
public double whenWillItBurn(double celcius) {
...
}
public boolean breakWeight(double x, double y, double z) {
return BREAK_FACTOR * shapeBasedBreak(x,y,z);
}
protected abstract double shapeBasedBreak(double x, double y, double z);
}
public class WoodenChair extends Wood {
protected abstract double shapeBasedBreak(double x, double y, double z) {...}
}

This gets the job done, with some quirks. First, we can talk about the exposed abstract method even though it’s not really part of the interface. The second is what it means to override whenWillItBurn/breakWeight something that doesn’t make sense in any of our discussed requirements.

These discussions are nit-picky, and there aren’t right answers, just a way to talk about consequences and options and see how the candidate thinks.

I’ll also see non-inheritance answers. These involve a common file of static functions and constants with no ties between the WoodenChair and WoodenTable. There’s still a question of tracking the common contract between pieces of furniture and that will lead to an idea like the following.

public class Wood {
public Wood(ShapeType shapeType){...}
    public double whenWillItBurn(double celsius) {
...
}
public double breakWeight(double x, double y, double z) {
switch(shapeType) {...}
}
}

Regardless of which solution I get, I can propose the other to the person. Then we can talk about comparisons and I start to see how much they care not just about getting from A to B, but how we get from A to B.

The candidate is more settled into what’s happening now and I’ll mess with their world again. I suggest that in addition to wooden chairs and tables there are steel chairs and tables with the same function requirements. The willItMelt functionality is the same for all steel objects. The willItBreak functionality though is f(x,y,z) * steelConstant where there is one value f’ for all tables and a different value f’’ for all chairs.

If someone is used to a language like python they’ll jump to mixins.

class Material():
def melt_func():
raise
def weight_factor():
raise
class Wood(Material):
def melt_func():
...
def weight_factor():
...
class Steel(Material):
pass
class Shape():
def shape_break(x,y,z):
raise
class Chair(Shape):
def shape_break(x,y,z):
...
class Table(Shape):
pass
class WoodenChair(Wood, Chair):
def when_will_it_melt(temp_c):
return super(WoodenChair, self).melt_func(temp_c):
def weight_breaks(x, y, z, weight):
return super(WoodenChair, self).weight_factor() * super(WoodenChair, self).shape_break(x,y,z)

The solution works and doesn’t have repetition of code, plus it can easily satisfy linters. Yet, they introduced the fragility of the diamond problem. We’ll talk about that and a very natural response is that there doesn’t seem to be a reason that either superclass would start having the same functions as the other, so there isn’t a problem. It’s also fair of them to point out that worrying about issues that don’t exist is a pretty good way to waste a lot of time.

We can dive into many of the concerns of OO, ones that are nicely explained in https://medium.com/@cscalfani/goodbye-object-oriented-programming-a59cda4c0e53.

In these situations I’m noting already their working style. How much do they care about the consequences of short term work, how much do they care about the experiences of their potential colleagues. How much do they care about code simplicity at all.

I will always take multiple inheritance away if they use it. Languages like Java don’t allow it, and languages like Golang don’t have inheritance at all.

We get to something like the following with the single inheritance.

public interface Material {
double whenWillItMelt(double tempC);
double breakFactor();
}
public class Wood implements Material {
double whenWillItMelt(double tempC) {...}
double breakFactor() {...}
}
public abstract class Furniture {
public abstract double whenWillItMelt(double tempC);
public abstract double breakWeight(double x, double y, double z);
}
public class Table extends Furniture {
public Table(Material m) {
this.material = m;
}
public double whenWillItMelt(double tempC) {
return this.material.whenWillItMelt(tempC);
}
public double breakWeight(double x, double y, double z) {
return ... * this.material.breakFactor();
}
}

Now we can talk about the power of composition and how that compares to inheritance.

More junior candidates will generally need a lot of hand holding because this level of code design is not something they’re familiar with. The good ones express a lot of interest and will start putting together conclusions about why they’d like one thing over another. The worse ones show apathy and concern only with doing what they think you want them to do or will fight for poor solutions without being open to expanding their point of view.

Good senior candidates should be directing the conversation and flow a lot more and have strong opinions and reasoning on why one solution is more maintainable than another. In general senior candidates perform better on this interview than junior ones. And it makes sense that an apt interview should favor people who’ve done the job longer and therefore would typically be better at the job itself.

There’s one more level this design question can take. Though I only approach use it if candidates breeze through the previous. The next step is composite furniture. Rather than a pure wood/steel piece they can be combined. For simplicity the whenWillItMelt becomes the smallest time of any material and the break factor for breakWeight is based on whatever material the position input is touching. This opens up the idea that the construction of a furniture piece is non-trivial and we can start talking about builder and factory patterns.

Overall, the experiences I’ve had with this problem have been the best predictor of how well we’re going to work together in practice. People who have done well have been colleagues I’ve been able to quickly trust as independent owners of their projects and become great company multipliers.

Not doing code design interviews? Try them!