Category: Uncategorized

I learned about currying today. It’s very cool, but I realized I had (sort of) been using it previously without realizing it!

For background, I work primarily in Java. Java 8 supports functions, but beyond some simple applications the syntax gets in the way of actually being able to do functional programming. I’ve also fairly recently been using the idea of specifications from domain driven design.

Specifications (specs) are just a way to separate business / domain logic into its own place, rather than leaving them as seemingly-random snippets or methods in other parts of your code. A spec might look like this:

There are a few things to comment about here. First, I don’t use specs quite how Fowler recommends. Namely, I don’t always use them as predicates (mainly because I haven’t needed to support composition).

Second, as written, this spec is kind of useless. That’s because it can’t be reused–every caller must specify exactly what colors they need to check for. This also means that you can’t pass the spec around to ensure that multiple applications are using the same spec. What we really want is this:

We now need to actually instantiate a spec in order to use it, but that instantiation ties a specific color to the spec. We can define, for example, an isRedSpec and pass that around.

This is currying! It’s just not solely using functions. The pro: it gives us the benefits of currying while still being idiomatic Java. The con: it’s not obvious that this is using currying, so others may not keep that in mind when maintaining this code in the future.

I try to set a high standard for myself. I like being tied to excellence. Even more, having high standards in a domain forces you to have a stronger understanding of that domain. Otherwise, you wouldn’t be able to discriminate between high quality and low quality.

As a team lead, I try to hold my team to the same standards that I have for myself. A core part of this is not allowing code reviews that don’t meet acceptable standards. I also include “nit” statements where changes are passable but it would be preferable if they were improved in some seemingly small way.

I do this to help pass on high standards. As a lead, it would be wrong for me to do otherwise. I wouldn’t be holding myself to my own standards, and I wouldn’t be helping my teammates to improve. As Jeff Bezos says, “people are pretty good at learning high standards simply through exposure.”

So, it was surprising for me to hear (secondhand) that some junior engineers were envious of other junior engineers whose team leads didn’t nitpick in reviews. Is this just a misalignment understanding, or is there a better way that these things should be taught?

There’s an important concept that good programmers have internalized, whether or not they realize it: they optimize their workflow.

The idea here is pretty straightforward: when we make a change, we want to know as soon as possible if our change is working as intended. It’s frustrating and time-consuming if we have to wait to see what happened. In other words, we want a tight feedback loop.

It’s pretty easy to see why quick feedback is useful. Something less obvious is that many tasks that we do involve feedback loops. Let’s look at some examples.

Tests

When we run a test suite, we’re looking for feedback as to whether or not our code is functioning correctly. How do we ensure that our test suite runs quickly?

The speed of each test depends on the type of test it is. Unit tests test isolated logic and are easy to configure and test. Additionally, we’re likely to have many more unit tests than any other types of tests. Unit tests need to be extremely fast since we want the entire test suite to finish quickly. At an average speed of .05s will take just under a minute to run. An average speed of .5s would still result in fast individual tests, but the entire suite would take over 8 minutes.

Integration and end-to-end tests are much more time consuming to run since they require more setup and interaction between different systems.

When we need to run all of our tests, we want to run unit tests first. They run the most quickly, so we have a much tighter cycle by looking for failing unit tests before looking for failing acceptance tests.

Development

As we write new code, we want to see that it’s working. This is easy for small or isolated components, but it can be quite time-consuming if there is a lot of setup that we need to do in order to see our change.

For example, suppose we’re building a shopping cart and want our cart to paginate when there are 15 or more different items in the cart. To test this, we’d have to add 16 different items to the cart, then navigate to the cart and ensure things are working correctly.

The process of adding these 16 items is a tedious setup step. We can speed this up by creating a script that adds items to our cart on its own. Or, we can just pre-populate our cart with items (i.e. hardcode it). Because we don’t care how our code got into a testable state, it’s completely fine to add some (temporary) code that quickly forces our code into a testable state.

Code Review

The standard code review process is to write some code, upload it to a review tool, and have someone take a look at it. We then have to wait for the review, which can take varying amounts of time based on our reviewer and the complexity of our change.

When it comes to our reviewer, we can always poke them if it seems like they aren’t looking at the review.

More importantly, however, is structuring our commit to make it easily digestible. Code reviews are harder to do when they deal with complex pieces of code and/or when the reviewer needs to juggle multiple things in their head to understand the code.

One of the most effective ways to do is to break commits down into digestible chunks. If your code has three main components, each one of them likely belongs in a separate commit. Breaking commits up limits their scope, which in turn makes it easier to follow what the commit is doing.

A few years ago, I wrote a series of articles on technical interviews. There was supposed to be a follow-up post where I actually walked you through a sample problem. Thing is, I never got around to it. Until now.

We’re going to be looking at a question that was a staple interview question at Yext for several years. The question is int2string, since it involves converting an integer to a string. Throughout this article, I’m going to be doing my best to explain not only the problem and solution, but also the various thought processes and pitfalls that I look for as an interviewer.

Let’s get started!

Interview Setup

First, let’s go through a few quick things to remind you of a pretty standard phone interview setup.

After a quick hello and introduction, I’m going to get you into some sort of collaborative coding environment. We used to use collabedit, which is just an online text editor with syntax highlighting. Today we use coderpad, which lets you also actually run code.

I’ll also ask you to choose a language to code in. I highly recommend you pick the language you’re most familiar with.

Problem Statement

Please write a method that takes in an integer and returns the string representation of that integer.

For example:
32 -> “32”
257 -> “257”

You can’t use anything that would make this problem trivial, namely the “toString” method (or equivalent in your language).

When explaining the problem, I’d say something nearly verbatim to the statement above. The first thing you should do when getting a problem is making sure that you understand the problem!

As an interviewer, I’m also looking to clarify anything that might be confusing. You should definitely ask questions if the statement isn’t clear.

For this particular problem, there are two main misinterpretations.

String Representation

The expected output is the same output that I’d get from Integer.toString, as shown in the two examples. I am not expecting an integer to be converted into English, i.e. 32 should not be converted into “thirty-two”.

toString

Seeing as this is an interview question, I’m not looking for something that would make the problem trivial. Very often, candidates would immediately answer with a solution that looks something like one of these:

Neither of these are what I’m looking for. They both trivialize the problem by using toString (implicitly for the second example).

Once the problem statement is cleared up, we get into actually solving it.

Finding a Solution

At this point the candidate will think through some possible ways to solve the problem. If you’re looking for practice, you might want to take a few minutes and brainstorm right now.

I personally don’t care if candidates think out loud. If it’s been a while and you haven’t said anything, I’ll ask you what you’re thinking so I can gauge how much progress you’ve made on the problem.

With that said, it can make it easier for interviewers to follow your train of thought if you talk out loud. Sometimes candidates mention a good idea but discard it. Having heard them mention it already makes it much easier for me to direct them back to that line of thought.

A Good Approach

After a few minutes, I’ll expect you to have some idea of how to approach the problem. I don’t expect it to be fully fleshed out, just that you’re going along the right train of thought.

For this problem, the approach I want to hear is that you’ll get the digits of the input, convert them to strings, and then concatenate them. Once you’ve gotten that far, I’ll give you the go-ahead to start coding. There are some pitfalls, but they can be worked out as you run into them.

Breaking Down Problems

If you are having trouble finding a workable approach, I’ll start guiding you with hints. I’ll probably be making mental notes as to how much guidance you need. It’s not a dealbreaker if I have to completely give you the basic approach, but you’ll need to do pretty well on the rest of the interview.

The int2string approach can be found with the tried-and-true strategy of solving an easier problem. In this case, inputs such as 1, 2, 3, etc. are as easy as it gets. (You could literally do an if statement.)

From there you can realize that if you can convert a single digit, you can break down the entire integer into digits and convert them one at a time.

Coding

As mentioned, our solution has three parts:

Break down the input integer into digits.

Convert each digit.

Concatenate the results.

As it turns out, each one of these steps has (potentially) some additional work you need to figure out. I generally expect you find and address them as you code.

As a general tip, coding is typically not very complicated for interview questions. If you find yourself getting into a complicated mess, that’s probably a symptom that something can be improved with your approach. As an interviewer, I’m hoping to see that you can recognize this and adjust.

Digit Break Down

How do we get the digits from our integer? The pitfall here is whether or not you decide to go left to right or right to left.

Many candidates try to go left to right. This involves figuring out the magnitude of the input integer and dividing by that amount. Candidates with approach would normally start writing code that looked something like this:

There are already a few subtle bugs with this code.

The first is that the Math functions return doubles. So you need to convert between doubles and ints which (1) can lead to errors and (2) is an indicator that you’re doing something too complicated.

The second is that 10^power will overflow for a large enough power, even if the input is a sensible input.

Most candidates taking this approach struggle a lot with the coding. I’ve seen a lot of different places where people struggle: figuring out what the basic code structure should look like, double/int conversion, computing the correct power, etc. These are all signs that the approach is too complicated.

If they keep trying to make it work, I’ll usually step in and mention that their approach seems like it’s getting complicated. This is a hint that reads: you should simplify or change your approach.

Going right to left radically simplifies things since you don’t care how many digits there are:

Convert Each Digit

The next step in our algorithm is to convert the digits we’ve found. In other words, we need to fill out this helper method:

There are several ways to convert a digit. Some of them are nicer than others, but I actually accepted all of the approaches below. They’re all O(1) since there’s only 10 possible inputs to this method.

Concatenate Digit Strings

This step isn’t complicated. However, there is a potential pitfall–because we parsed our digits right to left, you need to reverse the digits when concatenating.

Are You Happy?

If you got this far on your own (or with minimal hints), you’re doing a solid job. Interviewers will then ask you if you’re happy with your code.

Ideally, your answer would be no. That’s because you haven’t solved the problem yet–this solution only handles positive integers. A full solution should handle 0 and negative numbers.

If you answered yes, I would just ask how you would test your code. This hint has always gotten candidates to realize they weren’t handling all possible integers.

This type of thing is fairly common in interviews. Notice that the examples and everything else discussed so far have not mentioned 0 or negative numbers. That’s by design. (If you are unsure whether or not to handle these cases, you can always ask.)

Edge Cases

Zero

The zero case isn’t handled automatically since the while loop in the solution would terminate immediately. The method then returns an empty string.

I have had many candidates struggle with handling the zero case. That’s because they would always try to modify their algorithm to somehow handle the zero case.

That’s a terrible way to handle this issue. This is a special case, and it’s completely fine to handle it as such:

Negative Numbers

It’s not hard to modify our approach to handle negative numbers. However, there’s a bad approach that people tried surprisingly often.

With this approach, you change the while loop condition to be n == 0 and assume the loop handles digits correctly. Spoiler alert: it doesn’t!

The reason it doesn’t is due to how negative mods work. For example, -57 % 10 is either -7 or 3. I say either because the answer actually depends on your programming language! Regardless, both values are wrong since you actually want 7.

The good approach is pretty straightforward. (Hopefully you’re seeing a pattern here.) If the input is negative, note that it’s negative and multiply it by -1. Then convert the number as before. At the end, if the original input was negative, slap a negative sign on the front.

One Last Edge Case

At this point, I’d accept the solution. However, if you finished the problem quickly, I’d challenge you by telling you there was still one edge case remaining. Very sharp candidates would also realize that there’s still an edge case.

That number is Integer.MIN_VALUE. (This is actually language-dependent; this edge case doesn’t exist in python.) The reason it doesn’t work is that multiplying it by -1 results in an integer overflow.

I’m not including a snippet for this, but you would handle this by special casing it (exactly the same way 0 is special cased).

Final Notes

As mentioned, int2string was a staple question for our phone screen for several years. It was actually the first of two questions; we expected candidates to solve it in 20-25 minutes. The strongest candidates I’ve seen would solve it in around 10.

Hopefully this breakdown has given you a good idea of both how to approach an interview problem and what an interviewer might expect from you. Even though the problem is relatively straightforward, there’s a surprising amount of depth to it.

Debugging’s a critical skill that every software engineer needs to learn. However, it’s something that’s rarely taught. For many engineers, you kind of just have to figure it out (often through trial and error).

For me, this meant taking a spray and pray approach. I’d start with the most recent code I’d written or looked at and tweak some lines, then run my code. It didn’t work, so I’d pick a different method, change some stuff there, then run my code again. I kept doing this until I either found the bug or gave up for the day. This was pretty ineffective.

Over the past several years, I’ve gotten a lot better at debugging. I’ve debugged a lot more myself, watched my coworkers debug, and even given technical interviews that focus on debugging. Based on this I’ve identified a few things that really help in debugging more effectively.

Debugging Principles

Define Correct Behavior

In order to fix a bug, you first have to know what it means to be fixed!

This is normally a two part process: confirming that something isn’t working and knowing what it’s supposed to look like. At a high level, most people are pretty good at this. They’ll try to reproduce errors and they’ll have an example (or at least a description) of what functionality was supposed to happen.

Debugging is a recursive process–you’re continually hunting for lower level bugs until you find the actual issue. The place where people get in trouble is that they forget to keep defining correct behavior as they dig deeper.

I made this mistake a lot in the past. I’d identify that a part of my code was wrong and add a bunch of print statements to see the values of all my variables. Then I’d assume things were working if I saw anything get printed at all. Had I defined correct behavior properly, I would have known exactly what (and how many!) results I expected to be printed out.

Follow The Clues

There will always be some clues for you to investigate as you track down your bug. Use them!

In order to follow a clue, you first have to know it’s there. Some clues are obvious–you have an exception and a stack trace in your logs. These literally tell you where you should start your search.

Other clues are less obvious. That’s where defining correct behavior comes in. Anything that isn’t correct behavior is a clue. If you know your method ran 5 times but you only saw your debug message printed 4 times, that’s a clue. If you know that your string has length 4 but you don’t actually see anything, that’s a clue.

Once you’ve found a clue, you follow it by asking why that behavior is happening. If the clue isn’t self-explanatory, come up with some guesses as to what could be going on. Then check whether or not your guesses are correct.

Question Your Assumptions

Finally, always remember that you probably don’t fully understand the code. This is by definition–if you understood the code, you would already know what’s causing your bug!

The biggest indicator that you’ve fooled yourself into believing that you understand the code is when you find yourself stuck and thinking, “This should work because I know this piece of code does X.”

The issue is usually that that code doesn’t do X. It’s just that you believe it does X.

If you find yourself in this situation, you should stop and confirm what the code is actually doing. This will usually reveal something you overlooked, and you’ll be able to look more into that.

To be explicit, you should either use a debugger or some debug/print statements to see exactly what’s going on at your problem point. Don’t be afraid to get your hands dirty!

If you get really stumped, remember that your method doesn’t run in a vacuum! There might be something you don’t understand about the system architecture, things might be configured incorrectly, or your code might be reading faulty data.

The Approach

With these things in mind, how do you actually debug something? When your program runs, it’s executing your code one line at a time. Debugging involves finding the first line that ran incorrectly.

This might ring a bell–we have an ordered list of lines of code and want to find the first one that didn’t work the way we wanted it to. This is pretty similar to binary search!

Our basic approach is then as follows:

We define a starting point in our program where we know everything is still working correctly. We also have an end point where we know our bug has already occurred.

From there, we guess that there’s a bug at some point in between our start and end point. We investigate whether or not our code is still functioning correctly at that point.

If yes, we can repeat with the latter portion of the program. If no, we repeat with the earlier portion of the program. This continues until we find the buggy line. (If you ever get to a point where you think you’ve gone through every line of code, it might be that you originally defined the wrong starting point!)

How quickly you find your bug boils down to how well you can narrow down the code that you need to go through. Naturally, the more experience you have, the better you’ll be able to do this.

Regardless, keep in mind the concepts above–find clues, follow them, and make sure you understand both what your code should do and what it is actually doing!