Back to main page Traveling Coderman

The One-Hour Interview Process

613 views

Evaluating software engineers within the timespan of an interview is tough. You want to figure out if the applicant can help your team and at the same time make them want to work with you. And this ideally in an hour without requiring the applicant to beforehand complete a lengthy at-home assignment. In this post you find a guide to interview a software engineer applicant.

What's wrong with the status quo in interviewing? ๐Ÿ”—

In the last couple of years, I have sat on both sides of software engineering interviews and the interview process never felt quite right. Many companies have a multi-step process involving multiple interviews and at-home assignments. There are several issues I've seen with this process.

  • At-home assignments They require a lot of the applicants time. This is limiting the pool of applicants to those that are able and willing to invest that time. It puts persons in a full-time job, other duties outside of the job and those evaluating several companies at a disadvantage.
  • Focus on evaluating the applicant In an interview, both the company and the applicant are evaluating each other. If the interview isn't conveying realistic problems but instead uses an artificial coding problem, then you are missing an opportunity to make the applicant excited for your company.
  • Focus on evaluating skills instead of potential If you are evaluating the knowledge with a specific framework or library, then this requires a lot of preparation time for the applicant or you limit yourself to those engineers that have worked with that framework or library but might not be able to adapt once your tech stack changes. Instead, evaluate for timeless skills, for the ability to quickly grasp new new concepts and for soft skills.
  • Focus on deep knowledge instead of broad knowledge Especially pure coding assignments focus a lot on the same skill and evaluate it deeply, instead of touching several topics.
  • No different opinions after several interviews For a vast majority of applicants, the opinions of the different interviewers whether to hire or not to hire are identical. Every time this is the case, then one interview could have been sufficient.

The Goals of an Interview ๐Ÿ”—

Let's remind ourselves what we want to achieve with the interview process.

  • Assess the applicants knowledge: Are they able to work within a service that offers an HTTP API and/or a frontend? Can they work with an SQL database and can they work with your programming style?
  • Assess the applicants soft skills: Do they ask the right questions? Are they curious and eager for feedback? Do they collaborate and how do they react to mistakes or criticism?
  • Assess the seniority of the applicant: Should you hire them as junior, mid-level or senior engineers?
  • Make the applicant want to work at your company: What type of applications do you write? What kind of problems will they face in your daily work? What decisions will the applicant own? What skills will they learn and what can you teach them?
  • Give equal opportunity to every applicant: Interviews are stressful for applicants, you want them to feel as comfortable as possible such that it doesn't matter whether you are facing an introvert or extrovert, whether a person from a minority or majority, whether a person applying first-time or thirty years into the profession.
  • Minimize time: Applicants might apply at multiple positions, still work at a current job or have other duties to care for. You don't want to waste their time with a home-assignment or multiple lengthy interview sessions. You don't want to miss out on some engineers because they didn't have the time for the interview process.

The Framework ๐Ÿ”—

This guide assumes a role that involves the development of a product, which involves a backend server application with an SQL database, an HTTP API and a dedicated web frontend. For such a role, it is crucial to figure out how skilled the applicant is regarding HTTP APIs, general coding, frontend development and databases. Additionally, as a functional programmer, I'd like to assess for familiarity with functional programming paradigms. Each of these topics makes up a block of similar time. A block consists of questions or a problem statement that you as the interviewer ask or formulate within a conversation with the applicant. With each block, it is the idea that junior applicants can figure out an answer and that more senior applicants can go into depth outlining different aspects or multiple solution or initiate discussions on them. There are no clear correct or wrong answers.

Note Nervousness arises if the applicant does not know what you expect from them. Be upfront with the topics you want to cover and what skills you are looking for such that the applicant feels comfortable and isn't limited by their nervousness.

Each topic aims to figure out where the knowledge of the applicant ends. Some applicants might struggle with the first question of the block. In that case, try to lead them to an answer and then skip the rest of the topic moving on to the next topic. Afterwards, ask yourself if you can teach that missing knowledge to the applicant. Should they have known this considering their past experience? Were they curious about the answer they didn't know?

Note Don't reveal how many questions you want to ask in each topic! You want to be able to move on without leaving the applicant feel like they missed half of the things they could have known. If you feel like the applicant would appreciate the feedback, you can tell them afterwards in which topics you would usually have asked more and how they can advance their knowledge in these topics.

Without any further ado, let's look into the details of the five blocks.

APIs ๐Ÿ”—

  • Has the applicant knowledge about HTTP APIs, how to use them and how to model them?
  • Does the applicant ask clarifying questions and makes reasonable assumptions?
  • Convey to the applicant how you model APIs

Question Which common HTTP methods besides of GET can you think of and how would you use them?

With this question you can check if the applicant has either modeled or used an HTTP API. With explicitly mentioning GET in the question, you avoid confusion because of unfamiliarity with the term HTTP method. If the applicant answers this question without a blink of an eye, then you can talk with them about the properties of these methods, with for example two identical PUT calls leading to the same state as if only one of them would have been executed.

  • GET: Retrieve an entity or a list of entities
  • POST: Create an entity or send command
  • PUT: Create or exchange an entity
  • DELETE: Delete an entity
  • PATCH: Partially change an existing entity (Bonus)

Question Let's model an HTTP API of organizations and users where each organization consists of multiple users. There already exists an endpoint GET /orgs that returns a list of all organizations when called. Can you please list several endpoints you would introduce to allow creating, changing and retrieving of both organizations and users?

This question aims to figure out how much experience the applicant has in modeling HTTP APIs. There are multiple solutions which are justifiable, the most senior applicants will list name them with their advantages and disadvantages.

Note Only ask this question, if the applicant has answered the previous question confidently.

The main catch with this question is how organizations and users relate. The problem statement only states that organizations consists of users, it doesn't make any statement whether users can exist on their own, whether they can belong to multiple organizations or if they can change organizations. The most senior applicant will ask clarifying questions or outline to you the different ways how to model the API based on these assumptions.

If the applicant assumes that users are bound to organizations, then they'll likely come up with an API design similar to this one.

  • GET /orgs
  • GET /orgs/{id}
  • POST /orgs
  • PUT /orgs/{id}
  • GET /orgs/{id}/users
  • GET /orgs/{id}/users/{id}
  • POST /orgs/{id}/users
  • PUT /orgs/{id}/users/{id}

If they do and you feel like the applicant is able to discuss their solution confidently, you might want to challenge their design with a follow-up question.

Question Now a use case comes up that a user now changes its organization and we would like to cover this with the API. How would you approach this?

If the applicant came up with the solution modeling the user as a sub resource of the organization /orgs/{id}/users, then they might now change their mind and model the user as a top-level resource /users with the user entity referencing the ID of the organization { orgId: '123' }. Some applicants might also come up with a non-RESTful solution like POST /orgs/{id}/users/switch. That can be a starting point for a conversation about REST with more senior applicants.

  • GET /orgs
  • GET /orgs/{id}
  • POST /orgs
  • PUT /orgs/{id}
  • GET /users
  • GET /users/{id}
  • POST /users
  • PUT /users/{id}

Functional programming ๐Ÿ”—

This block aims to assess the ability of the applicant to utilize functional programming paradigms. You might want to skip this section if it does not apply to your software development philosophy.

  • Has the applicant applied functional programming before?
  • Establish that the coding challenge in upcoming blocks can be solved with a functional paradigm if this applicant sees it as suitable.

Question Across various programming languages, which ways do you know to model the absence of a value?

Most Java and JavaScript programmers are able to name null and/or undefined. Java programmers with some functional background should be able to name Optional from the standard library, JavaScript programmers might mention one of the similar options from third-party libraries. Java programmers might mention the null object design pattern.

  • null / undefined
  • Optional / Option / Maybe
  • Null Object Pattern (Bonus)

If the applicant mentions Optional, Option or Maybe, you can assume some functional background and check how deep the knowledge is. If the applicant does not mention it, you can skip to the next block.

Question Assume you have two existing functions Optional<User> lookupUserByName(String name) and Optional<Org> lookupOrgByUser(User user). Could you please implement a function Optional<Org> lookupOrgByUserName(String userName) that returns the organization of a user given its user name?

This question specifically aims for the usage of flatMap.

public Optional<Org> lookupOrgByUserName(String userName) {
return lookupUserByName(userName)
.flatMap(user -> lookupOrgByUser(user));
}

Applicants, who have heard of Optional but are coming from a more procedural programming style, might fall back to a solution with isPresent and get. This might not be a problem, you need to ask yourself the questions: Can you teach them? Will they be able to work with your code if it is using more advanced functional programming patterns?

public Optional<Org> lookupOrgByUserName(String userName) {
var user = lookupUserByName(userName);
if (!user.isPresent()) {
return Optional.empty();
}
return lookupOrgByUser(user.get());
}

Coding ๐Ÿ”—

The coding block aims to determine basic programming ability.

  • Is the applicant able to code? Do they struggle with syntax? How do they structure the code, do they find a starting point?
  • Do they clarify unclear criteria, think about testing and communicate during coding?
  • Can the applicant stick to existing coding conventions and adapt their style?
  • Convey a semi-realistic problem that could come up during work

This block expects the applicant to be convenient with a programming language but not with a specific framework or library. It formulates a problem that will likely come up during work in some way or the other: You have some data in the database, some other information available via an API and you want to apply some logic based on which you are sending out notifications.

Question We now would like to implement a daily job that sends out notifications to all users of organizations on their founding date. There is an external API that allows us to retrieve the founding date of an organization and there is an existing function to send out notifications.

It gives the applicant some existing models (User, Org and Notification) and some existing functionality (services NotificationService, FoundingDateService and OrgDao in Java, functions sendNotification, retrieveFoundingDate and selectAllOrgs in TypeScript) and expects the applicant to assemble these together into a solution.

Note Junior applicants might struggle to find a starting point. While a senior applicant should be able to find that starting point thyself, you might want to give a junior applicant a skeleton of a service with an empty method (Java) or a function with parameters (TypeScript).

These are the classes and methods you can provide to the applicant. The left out body is not relevant for the code the applicant needs to write, they can focus on the signatures.

class User {
public String getName() { /* ... */ }
}
class Org {
public List<User> getUsers() { /* ... */ }
public String getName() { /* ... */ }
}
class Notification {
public Notification(String orgName, String userName) { /* ... */ }
}
class NotificationService {
public void sendNotification(Notification notification) { /* ... */ }
public void sendNotifications(List<Notification> notifications) { /* ... */ }
}
class FoundingDateService {
public Date retrieveFoundingDate(String orgName) { /* ... */ }
public Map<String, Date> retrieveFoundingDates(List<String> orgNames) { /* ... */ }
}
class OrgDao {
public List<Org> selectAllOrgs() { /* ... */ }
}
class DateUtil {
public static boolean haveSameDayOfYear(Date date1, Date date2) { /* ... */ }
}
interface Job {
void execute();
}

In TypeScript, the existing code can look like this.

interface User {
name: string;
}
interface Org {
users: User[];
name: string;
}
interface Notification {
orgName: string;
userName: string;
}
async function sendNotification(notification: Notification): Promise<void> {
/* ... */
}
async function sendNotifications(notifications: Notification[]): Promise<void> {
/* ... */
}
async function retrieveFoundingDate(orgName: string): Promise<Date> {
/* ... */
}
async function retrieveFoundingDates(
orgNames: string[]
): Promise<Record<string, Date>> {
/* ... */
}
async function selectAllOrgs(): Promise<Org[]> {
/* ... */
}
function haveSameDayOfYear(date1: Date, date2: Date): boolean {
/* ... */
}

Beside the basic coding skills, this challenge also allows you to assess some other things.

  • Does the applicant consider performance and error-handling (sendNotification vs. sendNotifications and retrieveFoundingDate vs. retrieveFoundingDates). The single versions might be slower because they might result in single calls and they might fail individually.
  • Does the applicant utilize an imperative or functional paradigm?
  • Does the applicant tend to over-engineering?

A functional solution in Java could look like this.

public class FoundingDateNotificationJob implements Job {

private final OrgDao orgDao;
private final FoundingDateService foundingDateService;
private final NotificationService notificationService;

public FoundingDateNotificationJob(OrgDao orgDao, FoundingDateService foundingDateService, NotificationService notificationService) {
this.orgDao = orgDao;
this.foundingDateService = foundingDateService;
this.notificationService = notificationService;
}

public void execute() {
var orgs = this.orgDao.selectAllOrgs();
var foundingDates = this.foundingDateService.retrieveFoundingDates(extractOrgNames(orgs));
var today = new Date();
var notifications = createNotifications(orgs, foundingDates, today);
this.notificationService.sendNotifications(notifications);
}

private static List<String> extractOrgNames(List<Org> orgs) {
return orgs.stream()
.map(Org::getName)
.collect(toList());
}

private static List<Notification> createNotifications(List<Org> orgs, Map<String, Date> foundingDates, Date today) {
return orgs.stream()
.filter(org -> DateUtil.haveSameDayOfYear(foundingDates.get(org.getName()), today))
.flatMap(org -> org.getUsers().stream()
.map(user -> new Notification(org.getName(), user.getName()))
)
.collect(toList());
}
}

An imperative version (no judgment!) could look like this.

public class FoundingDateNotificationJob implements Job {

private final OrgDao orgDao;
private final FoundingDateService foundingDateService;
private final NotificationService notificationService;

public FoundingDateNotificationJob(OrgDao orgDao, FoundingDateService foundingDateService, NotificationService notificationService) {
this.orgDao = orgDao;
this.foundingDateService = foundingDateService;
this.notificationService = notificationService;
}

public void execute() {
var orgs = this.orgDao.selectAllOrgs();
var today = new Date();
for (var org : orgs) {
var foundingDate = this.foundingDateService.retrieveFoundingDate(org.getName());
if (DateUtil.haveSameDayOfYear(foundingDate, today)) {
for (var user : users) {
var notification = new Notification(org.getName(), user.getName());
this.notificationService.sendNotification(notification);
}
}
}
}
}

A functional solution in TypeScript looks similar to the functional solution in Java.

async function executeFoundingDateJob(): Promise<void> {
const orgs = await selectAllOrgs();
const foundingDates = await retrieveFoundingDates(
orgs.map((org) => org.name)
);
const today = new Date();
const notifications = createNotifications(orgs, foundingDates, today);
await sendNotifications(notifications);
}

function createNotifications(
orgs: Org[],
foundingDates: Record<string, Date>,
today: Date
): Notification[] {
return orgs
.filter((org) => haveSameDayOfYear(foundingDates[org.name], today))
.flatMap((org) =>
org.users.map((user) => ({
orgName: org.name,
userName: user.name,
}))
);
}

The imperative solution in TypeScript could look like this.

async function executeFoundingDateJob() {
const orgs = await selectAllOrgs();
const today = new Date();
for (const org of orgs) {
const foundingDate = await retrieveFoundingDate(org.name);
if (haveSameDayOfYear(foundingDate, today)) {
for (const user of users) {
const notification = new Notification(org.name, user.name);
await sendNotification(notification);
}
}
}
}

Frontend ๐Ÿ”—

If you are developing for a frontend or full-stack role, you might want to check for the applicants ability to work in the frontend. While the previous coding block represents a typical backend problem, it does not require any library or framework knowledge. Therefore, it is equally useful to assess the coding skills of a frontend developer. This block aims to assess the frontend skills without requiring a time-consuming coding problem.

  • Is the applicant able to conceptualize a frontend feature?
  • Does the applicant consider usability and accessibility?
  • How deep is the frontend experience of the applicant?

Question We now would like to introduce a search in the frontend to allow to find a user by their name. After a search term has been entered, a list of matching users should appear and it should be possible to select one of them to navigate to a detail view of that user. The endpoint GET /users already got extended to support a query parameter q. Can you elaborate how you would implement the frontend part of such a search such that it is highly usable and leads users fast towards the search result they were looking for?

This question can be answered in one sentence but also in a lengthy conversation. If the applicant considers the question answered after a sentence, try to challenge their solution or remind them of different aspects or edge cases.

A standard solution should have these or similar properties.

  • A text input field to allow enter a search term
  • A backend request is executed either when
    • the user has stopped typing for a duration
    • the user has pressed enter or clicked a search button
  • When a backend request is executed, there is some loading indicator
  • When a backend request returns with results, the loading indicator disappears and a list of results is shown
  • A shown result can be clicked, opening a detail view

Additionally, other things might be mentioned or can be utilized by you to check the depth of the applicants experience.

  • If there are no results, then a text is shown indicating that there are no results, asking the user to refine the term or making an alternative suggestion
  • Once a loading indicator is shown, previous results are not shown anymore
  • If a search term is empty or less than a number of characters, then no search is started
  • When a user continues typing during a backend request, then the request is stopped and the loading indicator disappears
  • The shown results indicate why they have been returned (Highlighting of search term, etc.)
  • The user can use the up and down keys to go through the list and press enter to open the detail view. The user can still type letters to refine the search term. (The focus never leaves the input field)
  • On hover over a result it is highlighted

Databases ๐Ÿ”—

This last block aims to assess the familiarity with modeling data in a relational database. You might want to exchange this block if you are primarily using document-based databases.

  • Is the applicant familiar with models in a relational database?
  • Is the applicant able to interact with an SQL database?
  • Is the applicant able to work in an existing database schema?

Question Let's take the example of orgs and users and assume that both entities have only an id and a name. Furthermore users are always within exactly one org. Which tables and with which columns would you create in an SQL database to model these two entities?

This question covers basic relational modeling with one table referencing the other table in a one-to-many relationship. Applicants might struggle with how to represent these tables. Remind them that already table names, column names and their types are sufficient. If they continue to struggle, you might want to give them the orgs table and ask them to give you the users table based on that.

  • Table orgs: id primary key, name text/varchar
  • Table users: id primary key, name text/varchar, org_id foreign key

Question We now want to query the database for a combined list of all user names of the organizations Google and Microsoft. How would such a query look like? You can use plain SQL or your preferred way of how you would make such a query.

This question aims at the applicant doing some form of Join. Since there are multiple ways to do a Join, there can be a lot of variations.

Note I would recommend asking this question even if the applicant was not able to model the two tables in the previous question. The applicant might not have modeled a database thyself, but they might be able to query against an existing database.

select u.name from users u, orgs o where users.org_id = orgs.id and o.name in ("Google", "Microsoft")

This question also aims at the usage of in. In case the applicant uses two equality comparisons, you might want to challenge them with a new requirement of a dynamic list orgs of organizations passed to the query.

Question We now realize that users are not necessarily in a single organization but can be in multiple. The database is running in production for thousands of users around the globe and downtime and disruptions should be avoided. How would you approach such a change on a high level step-by-step?

Junior applicants will very likely not be able to answer this question, you might want to leave it out even if they answered the previous question successfully. This question can help you though to assess the seniority of the applicant.

  • Add an additional table userorgs: user_id, org_id and primary_key(user_id, org_id)
  • Persist new org changes of users in both the new table and the old column
  • Migrate all existing org_ids from users in that table
  • Change select queries to use the new table instead of the org_id column
  • Remove the old org_id column

Conclusion ๐Ÿ”—

We have discussed the problems with existing interview processes and based on the goals of an interview outlined a flexible framework to assess an applicant while minimizing the time interviewees and interviewers need to spend on it. The framework consists of several blocks that can be dynamically adjusted before and during the interview to figure out the knowledge and the level of seniority of the applicant. I hope this guide helps you become a better interviewer and to evaluate more applicants in less time for everybody.