I recently tried my hands at a technical interview at a company to see what more recent front-end interviews are like.

It was a good way to get my feet wet and see where interviewing for the rest of the industry has gone, being that Google's format hasn't changed significantly in the past decade.

In this post, I'll go over the following various aspects of a great candidate by drawing inspiration from my recent experience as the person interviewing for a company:

  • The high-level approach for interviewing
  • What technology to choose
  • Understanding the problem
  • Decomposing the problem
  • Navigating changing problem requirements effectively
  • Debugging

The High-Level Approach

All technical interviews are some version of solving a problem in front of someone else.

There's a good reason for this. Solving a problem for someone else means you show, not tell, your problem solving process. The problem solving process is essential to software engineering.

In theory, your effectiveness at solving a small problem is indicative of how you solve larger complex problems on the job.

This means you want to optimize your interview approach to best show your problem solving process.

  • You want to decompose complicated, larger problems into the smallest workable chunk you can complete quickly (you only have 45 minutes or so.)
  • You want to talk through the quick iterative decision making, not keep the interviewer in the dark.
  • You want to have a strategy for when you'll defer a seemingly tricky part of the problem for later, versus spend time thinking about it now so you'll know how to tackle it later (and be able to explain the justification on the fly as you go to keep the interviewer engaged.)

Here is a real world example interview problem I faced.

Given this array of data objects, write some javascript and html that will render them in an unordered list.

Disclaimer: I won't get into the actual problem context (like what the data objects were modeling,) out of courtesy to the company that I interviewed at, but the lessons from thinking about the abstract problem is relevant and applicable to most other interviews.

The Specifics

My point here will be to highlight key decisions I made in service of the objective of showing my problem solving process, not necessarily give out my exact answer for the entirety of the problem.

How to Choose a Technology

Immediately, I thought of all the different ways that I could accomplish this but the interviewer gave me some ideas:

  • I could use plain JavaScript
  • I could use a front-end framework of choice, like React.

I chose to go down the plain JavaScript path since, while I enjoy working with React, I figured it was a good challenge to work with just the DOM API to show the interviewer how I would google around and debug problems. Remember: The point of the interview is to show your problem solving process, not necessarily to show familiarity with a tool.

Moreover, if the interviewer really wanted to see my React skills, they likely wouldn't have presented me with the option to choose the technology.

Interviewing in a technology-agnostic fashion as you won't get tripped on library-specific intricacies that might send you down an unexpected rabbit hole.

I also figured, the problem couldn't be so difficult or lengthy that I would absolutely need an abstraction like React to do the DOM rendering part of the problem. Otherwise, they would probably just tell me to use React.

Understand the Problem

Make sure you read the question a couple of times and ask any questions that come to mind. You should thoroughly comprehend what the question is asking. You'd be surprised at just how many candidates make this mistake.

This is critical as you don't want to avoid making any assumptions in an interview.

  • Assumptions lead to inappropriate expectations.
  • Poor expectations lead to poor solutions (not actually solving what was asked.)
  • Poor solution means you automatically lose points, regardless of how well you solved the problem, because you solved the wrong problem.

Don't make the mistake of solving the wrong problem.

Clarify all problem requirements at the beginning.

For my problem, I asked questions and paid special attention to things like:

  • Does each data item need to be in an <li> tag?
  • Should the entire list be surrounded in an <ul> tag?
  • I took note of the exact text they wanted me to put inside of the <li> tag. (Attention to detail)
  • Do I need to style the unordered list?

How to decompose a problem

Problem decomposition is about identifying the various chunks of work that need to be done to compose the full solution. It is easier said than done, especially with the pressure of an interview, but you want to map out the overall solution to the problem before diving into coding.

For this question, we have several high level things that need to be done:

  • Iterate over the list of data entries
  • Create a UI element for a single data entry
  • Use javascript to dynamically put things onto the DOM (wire up the generated UI elements so that it shows up in the HTML output)

Each of these "chunks" of work can really be encapsulated into a function.

Since this is a list of data objects, rendered as an <li> item in HTML, and a JavaScript interview, I figured the most appropriate place to start was to create a function that returned an <li> item:

function createListItem(data) {
  const item = document.createElement('li');  
  // Code for setting the text of `item`
  return item;
}

Now I had an easily testable block of code that would be the very bottom piece of the solution, and I could work my way up.

I purposely didn't start at the top architecting the application structure, as that would detract from my primary objective: To arrive at a working solution and explain how I got there. The code structure is surely important, but it's not a critical deciding factor in most interview scenarios, so this takes a back seat.

While there's no right or wrong way to build an application (top-down or bottom-up), but you want to take the path of least resistance to make quick iterative progress in an interview. The perceived pace at which you progress through small challenges affects how your overall performance is perceived, so you want to quickly solve a series of small challenges that all come together to solve a larger problem.

In this situation, I figured that as the application would get more complex, I expected more opportunities to show my code organizational skills. But we were at the very first portion of the problem when the interviewer is really trying to get immediate verification that you have basic skillsets before diving deeper into more complicated stuff, so I started simple.

I iterated over the data collection, called the createListItem function I created, and appended the <li> items into a <ul> tag:

function createList(data) {
  const list = document.createElement('ul');
  for (const item of data) {
    list.appendChild(createListItem(item));
  }
  return list;
}

And then I stuck this in an createApp function that would mount this list onto the DOM, and then initialized it:

function createApp(data) {
  const app = document.createElement("div");
  app.appendChild(createList(data));
  // I could add things here as necessary as the problem evolves.
  return app;
}

function init() {
  const root = document.getElementById("app");
  const app = createApp(DATA);
  root.appendChild(app);
}

init();

I got through the first part of the problem without issue. It's essential that you get to this part of the interview smoothly as this is one of the gating points of most technical interviews.

If you can't get through the "warm up" portion of an interview, chances are the interviewer will stop you or let you continue but having already made up their mind on how the interview went.

This brings us to another critical factor to interview performance:

How to navigate evolving problem requirements

The above was just part 1 of the interview. The entire problem was actually 4 parts, and each one challenged me to solve a new problem. Here's how the interview question evolved:

  • Part 2: Apply some inline styles to the text of the <li> element based on its data.
  • Part 3: Create a search input box that would filter the list.
  • Part 4: Refine the search behavior to be more intuitive.

Part 2 was relatively simple as this was just showing how to use conditional logic to apply one style over another and vice versa, based on the data provided.

Part 3 is where things got interesting. Because the transaction data would no longer be a static list, but a changing list based on the input query, I had to solve the following abstract problems:

  • How to re-render the unordered list as the filtered results changed
  • How do I get the data from the text input and use that as the query for filtering the list

This is where the code organizational skills come in. We could also go down a deep rabbit hole of DOM diffing an optimizing how to write DOM re-rendering code. There's so much to do and so little time.

In an actual interview, this is a critical juncture in the road. You have to decide what aspect of the problem you want to prioritize. This is also a good time to get feedback on the exact direction you should go.

So I started out getting a feel for what the interviewer wanted me to do:

"So this list needs to be re-rendered," I started.

"And I could re-render them by just removing every list item and putting new ones back on the DOM, or we could do some kind of diffing to figure out what needs to be removed?"

The interviewer quickly responded: "No need to do some kind of virtual DOM or diffing implementation. That's obviously not in scope for the interview."

While I actually knew that this was a way more complicated problem than what was likely being asked of me, it was a good opportunity to clarify and make it explicit. As an interviewer, I appreciate this kind of dialogue because it shows that the candidate generally cares about the details.

By asking these questions, I'm able to show:

  • I comprehend the problem beyond just the most basic requirements.
  • I'm thinking about optimizations and how it affects the end-user ultimately.
  • Hints at my depth of knowledge when it comes to front-end development.

These are all signals that are valuable as an interviewer, and all it took for me to uncover this was asking a clarifying question.

It also got me back on track and focused on what I was actually trying to solve. It seemed like the interviewer wanted me mostly to just get the program to fulfill the requirements, and so I did exactly that.

Failure to navigate an evolving problem requirement contributes to a poor rating, so always remember to ask clarifying questions, care about the details, but be judicious in how you spend your time.

Always stay focused on solving for the requirements that they give you without getting overly concerned on specifics that don't actually change your output.

How to be an effective debugger

I'm no longer a novice when it comes to programming, and the thing that I noticed that I do differently from people who are new, is that I've gotten intimately familiar with how to debug.

For example, my code for removing all list items wasn't working as expected:

for (const li of ul.children) {
  li.remove();
}

Can you spot the problem? It's such a simple, 3-line block of code, but there's a really messy bug here.

Here's what I was seeing in the console

// Given this list:
// <ul>
//   <li>1</li>
//   <li>2</li>
//   <li>3</li>
//   <li>4</li>
//   <li>5</li>
//   <li>6</li>
//  </ul>

for (const li of ul.children) {
  console.log(li);
  li.remove();
}

// Console:
// <li>1</li>
// <li>3</li>
// <li>5</li>

What gives?

You might already be onto the answer, but here was my thought process:

  • Hm, that's weird. I've got a list of 6 things, I'm looping over it, and only see 3 things when I log each one.
  • It's skipping over every other item, but a regular for of loop is definitely not supposed to skip over every other item.

What you do next is indicative of how familiar you are with debugging.

I didn't have to think twice.

I immediately tried commenting out the line: li.remove(). Sure enough, all 6 things in the list showed up in the console output, and the problem was clear:

  • li.remove() mutates ul.children, because ul.children is a reference to the children array of HTML elements inside of ul. li.remove(), given that li is a child of ul, will mutate this list.
  • I had a live list problem. The issue was that I was iterating over a list that was being mutated as I iterated through it.

The live list problem is easily fixed by creating a new reference to the array that you will be mutating:

for (const li of [...ul.children]) {
  li.remove();
}

In JavaScript, this is as simple as "spreading" (...) out the contents of the array I want to iterate into a new array literal, which gives the loop a new array reference to use on each iteration. And everything worked as expected.

The particular fix isn't what's interesting though.

The real insight is how I knew to comment out li.remove().

There's a meta process that you can learn for how to arrive at the right insight when debugging a problem:

Ask The Debugger's Question

When you first get started out coding, you have to make explicit how you'll go about solving a problem because you aren't intimately familiar with the debugging process.

You don't know how the runtime is actually behaving, yet. You don't know what assumptions you're making. You don't know where your code is going wrong, and you don't know where to start debugging it.

There's a way you can deduce where you've gone wrong. It's a simple one-question framework, and it works every time in any debugging scenario.

What assumption am I making that justifies my expectation that my program will do X?

And then, you verify that that assumption.

And if your assumption is proven wrong but you still aren't sure what the root cause is, you simply ask the question again except now your previous assumption is the expectation, and you have to identify the assumption you made that justified that expectation.

It's a recursive question. (How appropriate.)

In my example, I was making an assumption that ul.children would be an array of 6 items, and that the for loop would iterate through each one.

But on the second iteration of the for loop, I was very clearly on the 3rd list item.

What justified my expectation that ul.children would be an array of 6 children? I figured it would be 6 children because that's what it is when I start the loop and it would stay that way.

But ul.children is referenced during each iteration of the loop when the interpreter determines what the next item will be in the iteration. This means that anything I do inside the loop, if it changes ul.children, would cause my original assumption that ul.children is a 6-item array would be incorrect.

So now, my new expectation (hypothesis) became that ul.children is being mutated inbetween each iteration. How would I verify this?

There is only one line that could potentially do this, and that's the 2nd line as it's the only line in the body of the for loop:

for (const li of ul.children) {
  li.remove(); // <-- this line.
}

And so commenting out is a quick way to verify the behavior of whether the length of ul.children is changing during iteration. That landed me on the root cause of the bug.

Don't forget to test the code.

I often see candidates make this mistake of not testing their code thoroughly enough. In my case, the requirements were rather simple and as long as I could get my program to behave the way it should on the UI, I knew I had completed the requirements.

But I always made it a point to test code when I thought I was finished by actually driving the UI, inputting text, seeing if the list updates, etc. Make a point to:

  • Test specific functions that are purely logical
  • Test a variety of inputs and think through a few edge cases.
  • Use the browser console to run the function (doesn't have to be a literal test case that's written.)

The general rule of thumb: if you can quickly verify a function's behavior, do it. If you can't quickly verify a function, don't test it for now (it'll be tested implicitly when you run the whole program.)

Master this problem solving cycle and you will never be stuck again in the same way during an interview.

If you think about programming interviews as a "solve the problem or you fail", it's a bit too black-and-white and the only modes of thinking that it allows is a success-failure dichotomy.

While it's not possible to practice a process that guarantees you always solve a particular problem, deliberately practicing the problem solving practice gets pretty darn close to ensuring we stop messing up the process of problem solving.

Being able to solve the problem being asked is heavily weighted and matters a lot, but it's not the only signal the interviewer is looking for. Put another way, if you only solve the problem but fail on several other key dimensions, your chances are slim.

I like to think of programming interviews as more of a series of small sessions put on repeat that cover the problem solving process:

  • Problem comprehension
  • Problem decomposition
  • Solution ideation
  • Dialog with interviewer on direction
  • Implementation
  • Debugging
  • Testing

And these 7 dimensions are essentially repeated every time the interviewer evolves the problem requirements. Solving any problem really requires each of these steps regardless of how implicit or explicit you might make them to your interviewer.

The way you get better at this process is to simply practice applying this process, and make this process explicit. When solving any problem (not just in the programming context,) try to be cognizant of this process.

The better you are at recognizing and navigating this process from comprehension to testing and validating your hypothesis, the better you'll be at interviewing.

How to Ace Your Next Technical Interview