Skip to main content

The Case Against Coding Interviews

Continuing my reflections on how we can better hire software engineers, I'd like to next address the topic of coding interviews. They're a cornerstone of the interview process for software engineers and take many forms, including algorithm challenges, whiteboard sessions, and take-home exercises. On the surface, the practice sounds reasonable. If we're going to pay someone to write code for us, we should verify they know how to program, right?

The core problem is that coding interviews focus on superficial indicators of skill rather than the qualities that matter in real-world engineering. They're a tradition that persists not because it works, but because it feels like we're doing something rigorous. Meanwhile, engineers are increasingly refusing to participate, leaving us with a less diverse talent pool.

So, if coding interviews frustrate the very people we want to hire, and they don’t generate the insights we actually need, why are we still doing them? It’s time to adopt hiring practices that are genuinely insightful, respectful, and better aligned with what software development actually is today.

What We Test vs What We Need

Software development has changed drastically from what it was even a decade ago. Today's engineers spend less time writing algorithms from scratch and more time navigating complexity. They debug legacy code, integrate with third-party APIs, adapt to changing requirements, balance technical trade-offs with user needs and deadlines, and refine code through feedback, refactoring, and repeated cycles of improvement. The work is collaborative, contextual, and iterative.

However, a typical coding interview might ask the candidate to find the nth term of the Fibonacci sequence or invert a binary tree. This tests whether the candidate knows syntax and can recall a memorized solution, but it doesn't show us what we really want to know. Can they reason about complex systems? Can they collaborate effectively with team members? Can they adapt their approach when requirements change mid-project?

Unfortunately, coding interviews miss the broader skills that actually matter. They test only a narrow slice of programming ability, while overlooking the collaborative, creative, and strategic thinking that is critical for success. And if we're going to spend 20-30 minutes with a candidate, we want that time to be as productive as possible.

The Candidate’s Perspective

There’s also the issue that many engineers don't perform well under observation. Even highly capable engineers can find themselves blanking or fumbling through trivial mistakes when they're expected to perform in real time while someone watches and judges them. Of course, software engineering isn't without pressure, but the nature of that pressure is fundamentally different.

Engineers face deadlines, the pressure to deliver quality solutions, and the pressure to make good architectural decisions. These are productive pressures that drive meaningful outcomes. They also come with support systems: teammates to collaborate with, documentation to reference, time to research solutions, and the ability to iterate and improve. By contrast, the pressure of coding interviews is purely performative. It's the pressure of being judged on artificial tasks under artificial constraints, and can be closer to stage fright than any professional pressure.

It’s also important for us to “read the room.” A growing number of engineers are pushing back against coding interviews, and some flat-out refuse to do them. They're not frustrated because the challenges are difficult (they’re not); they’re frustrated because the challenges are irrelevant. And the more experience a candidate brings, the more likely they are to feel these exercises are insulting.

Imagine a candidate with 15 years of experience. Their resume shows fluency in multiple programming languages, cross-functional leadership, and critical contributions to multimillion-dollar products. Then we invite them to screen-share and ask them find the repeated digit in an array. Of course, don’t intend to send a message of disrespect, but that’s exactly what’s received: We don’t actually value the skills you’ve built. We just want to see if you’ll jump through this hoop.

Rethinking Trust and Verification

All this isn’t to say we should hire without any form of verification. But there are more effective ways to understand a candidate’s abilities than putting them through a coding interview. We don’t ask a plumber to solve a trick question about pipe theory. We don’t ask a secretary to type 90 words per minute while someone watches. In nearly every other profession, we evaluate people based on their experience, portfolio of work, peer references, and how well they fit the role.

So how should we assess software engineers? The answer lies in evaluating the capabilities that drive real-world engineering success.

Portfolio-based evaluation focuses on actual work. Reviewing GitHub repositories, side projects, or open source contributions can provide insight into real-world skills. Ask candidates to walk through a project they're proud of and explain their decisions and trade-offs. This reveals both technical ability and thought process. Look for evidence of good practices like clear documentation, thoughtful commit messages, and consideration for maintainability.

Code review exercises simulate genuine responsibilities. Present candidates with a piece of anonymized code from your codebase and ask them to review it. This verifies their ability to read code, identify issues, and provide constructive feedback.

Architectural discussions can reveal systems thinking. Present a problem your team has faced and discuss different approaches to solving it. This shows how candidates think about trade-offs, scalability, and design. It's also more engaging for experienced candidates who want to demonstrate their strategic thinking abilities.

Trial periods or contract-to-hire arrangements offer the most honest assessment. Where portfolios show how someone has approached problems in the past, trial periods reveal how they collaborate, communicate, and grow in real time. Hiring promising candidates for a short-term contract or probationary period gives both sides a chance to evaluate fit with real problems. While this approach requires more upfront investment, it leads to much better hiring decisions.

Time for Change

Coding interviews favor candidates who are comfortable with performative displays of knowledge, but the best engineers aren't always the best performers. By shifting our focus to real work and collaborative problem-solving, we can create room to highlight different kinds of excellence.

The goal isn't to make hiring easier. It's to make it more effective, more respectful, and more aligned with the work we actually expect people to do. We're hiring for systems thinkers and builders, not performers, and it's time our hiring practices reflected that.

Of course, change is never easy, especially when it challenges long-standing practices. Moving away from coding interviews takes courage and leadership. It requires letting go the comfort of tradition in favor of more nuanced evaluation methods, trusting that a candidate's portfolio, references, and ability to discuss complex problems can tell us more than watching them fumble through a whiteboard exercise.

Comments

Popular posts from this blog

Writing a Minimal PSR-0 Autoloader

An excellent overview of autoloading in PHP and the PSR-0 standard was written by Hari K T over at PHPMaster.com , and it's definitely worth the read. But maybe you don't like some of the bloated, heavier autoloader offerings provided by various PHP frameworks, or maybe you just like to roll your own solutions. Is it possible to roll your own minimal loader and still be compliant? First, let's look at what PSR-0 mandates, taken directly from the standards document on GitHub : A fully-qualified namespace and class must have the following structure \<Vendor Name>\(<Namespace>\)*<Class Name> Each namespace must have a top-level namespace ("Vendor Name"). Each namespace can have as many sub-namespaces as it wishes. Each namespace separator is converted to a DIRECTORY_SEPARATOR when loading from the file system. Each "_" character in the CLASS NAME is converted to a DIRECTORY_SEPARATOR . The "_" character has no special ...

Safely Identify Dependencies for Chrooting

The most difficult part of setting up a chroot environment is identifying dependencies for the programs you want to copy to the jail. For example, to make cp available, not only do you need to copy its binary from /bin and any shared libraries it depends on, but the dependencies can have their own dependencies too that need to be copied. The internet suggests using ldd to list a binary’s dependencies, but that has its own problems. The man page for ldd warns not to use the script for untrusted programs because it works by setting a special environment variable and then executes the program. What’s a security-conscious systems administrator to do? The ldd man page recommends objdump as a safe alternative. objdump outputs information about an object file, including what shared libraries it links against. It doesn’t identify the dependencies’ dependencies, but it’s still a good start because it doesn’t try to execute the target file. We can overcome the dependencies of depende...

A Unicode fgetc() in PHP

In preparation for a presentation I’m giving at this month’s Syracuse PHP Users Group meeting, I found the need to read in Unicode characters in PHP one at a time. Unicode is still second-class in PHP; PHP6 failed and we have to fallback to extensions like the mbstring extension and/or libraries like Portable UTF-8 . And even with those, I didn’t see a unicode-capable fgetc() so I wrote my own. Years ago, I wrote a post describing how to read Unicode characters in C , so the logic was already familiar. As a refresher, UTF-8 is a multi-byte encoding scheme capable of representing over 2 million characters using 4 bytes or less. The first 128 characters are encoded the same as 7-bit ASCII with 0 as the most-significant bit. The other characters are encoded using multiple bytes, each byte with 1 as the most-significant bit. The bit pattern in the first byte of a multi-byte sequence tells us how many bytes are needed to represent the character. Here’s what the function looks like: f...