A Tech Interview That Doesn't Suck
Hiring is hard. Damn hard. The smaller the team, the higher the stakes. The right hires create leverage that helps everyone ship faster; bad hires drag productivity down for everyone. The Tech Test — a practical assessment of your coding skills — is the beating heart of the software engineering interview loop. From the other side of the keyboard, it isn’t as stressful, but it’s tough to make a binary decision based on imperfect information — a brief look at your coding skills. With dozens (or hundreds) of candidates in play, it’s understandable why most tech companies fall back on LeetCode to enforce a lowest common denominator of technical skills. But I wanted to find a better way. What makes a good Tech Test?First, let’s agree on what we mean by a “good” tech test. For candidates, it needs to be brief, not overly stressful, and give me the opportunity to demonstrate my skills. For interviewers, it needs to effectively test for skills needed for the role, and allow us to appropriately level candidates. Types of TestI’ve run a lot of hiring loops in my time. There are a few common flavours — the take-home test; the theory interview; the live coding exercise. Let’s get this out of the way quickly: in the age of ChatGPT, take-home tests are a massive waste of time. Be better. Theory interviews have their place, particularly at specialised or very senior roles, but I’m mainly trying to focus on the coding side here. In my opinion, a solid interview loop contains a bit of both: you can design your coding exercises as jumping-off points to go deeper into theory. Live-coding exercises are really the only game in town when it comes to assessing coding skill, so let’s focus on optimising this. There are 2 schools of thought when it comes to coding exercises:
Testing for fundamentals sounds, on paper, the ideal way to assess candidates. You want engineers that can solve problems, so give them a problem to solve. In practice, however, this takes the form of an algorithmic interview, where the solution is some kind of data structure or algorithm. This rapidly descends into the highly game-able world of LeetCode, where candidates work out the answer because they have seen the question before. I’ve found the most accurate positive hiring signals to come from live coding exercises that closely approximate day-to-day work. My favourite format looks a little like this:
The Critical PathA common issue with tech interviews is the critical path. Let’s say your interview starts with a 403 HTTP error returning from an API call, due to a CORS issue. If you’ve handled this issue before, you might breeze through and look like a genius. If not, you’ll need to flex your problem solving muscles. This can be a pretty nice approach — testing debugging and problem solving skills is essential — but if the rest of the test is inaccessible without solving the CORS issue, then you might be rejecting solid candidates* who just happen to be weak on browser security mechanisms.
Unless the problem might reasonably be universal knowledge for seniors in your field, you want to avoid specific gotcha questions early on which candidates could fail by default. This is the last time I’ll mention it, but that’s mostly why I don’t like LeetCode. The whole problem is a critical path — you’re blocked if you haven’t seen that specific dynamic programming problem before. We like to act like we’re inventing a novel solution, but it’s all kayfabe. Open-ended test formats avoid a critical path, so candidates can’t get trapped in the sand-trap of a trivial knowledge gap. This also means you can stack the test with several types of problems and several levels of complexity. This allows more junior candidates pick and choose where to demonstrate their skills; while very senior candidates can demonstrate their mastery over your feeble attempts at gotcha questions. That’s right — this approach to the tech test unlocks easy levelling. There’s one final principle to consider: if you’re designing a test you’re going to be watching for 10 hours a week, it should be enjoyable for you to administer. Naturally, I themed this test around Bloodborne, my favourite FromSoftware game. Designing The TestIntroducing… the non-crappy tech test.
Every iOS technical test is essentially the same: “fetch data from an API and display in a table view”. But there’s a lot of nuance we can layer onto this approach. For modern iOS roles, we’ll probably heavily involve SwiftUI and Swift Concurrency. We want to give candidates the opportunity to demonstrate junior, mid-level, or senior competency, as well as demonstrating theory knowledge, debugging, and core problem solving skills. How are we meant to test for theory? We work backwards: Contrive situations where the solution requires a theoretical understanding. Let’s start building up the test. Basic Networking & Debugging
I’m fond of this question because it’s a pretty basic bit of problem-solving that’s universally applicable for any software engineering candidate. The main view at the start of the app displays a collection of boss monsters.
The view model contains pre-populated services, some The interview will end very quickly if the candidate doesn’t know to call load inside a After seeing a crash, a candidate should look into the service and find an empty They can implement a standard HTTP network request, and might run into a A solid candidate will set breakpoints (or This starter problem neatly allows candidates to flex their iOS networking and debugging fundamentals, where they should be able to call the API, notice the auth issue, slap a header on the HTTP request and decode the JSON without breaking a sweat.
With the data fetched, the candidate is treated to this beautiful barebones layout. They’ll soon give it a lick of paint. SwiftUI Skills
When testing candidates on SwiftUI, we obviously need to check they can build and structure views. Fortunately, Apple defaults do a lot of heavy lifting, so it’s very quick to make a basic UI if you know what you’re doing — we can level candidates into basic If we were hiring for an advanced SwiftUI role, we might even create a design spec that requires
After creating the design (and hopefully not spaffing away 75% of the allotted time), candidates can answer the age-old question: StateObject vs ObservableObject. If they respond with the cop-out
This more advanced task separates the wheat from the chaff. The answer requires a solid theoretical understanding of when redraws are triggered across SwiftUI views. Since I’m being nice, I added Leaving this clue out, and forcing devs to profile the problem, is also perfectly valid, but implementing the fix requires relatively strong understanding as well as decent refactoring skills. Swift Concurrency Aptitude
The way the original project is built has a common problem — warnings around updating UI from a background thread. This simple threading problem serves as a nice warm-up to more advanced concurrency issues later on, but any level of candidate should be able to explain the problem. The solution is to mark the view model, or the
Our initial implementation in Solving this displays aptitude for tools in Swift Concurrency aside from the basic Even better, this task serves as a natural segue into my favourite theory iOS interview question:
Now we get to the hardest concurrency question of the lot.
The basic auth service begins life looking like this: The print statements act as breadcrumbs to illustrate the bottleneck: the two API calls are separately calling and returning API tokens. In a real production app, this might cause issues like higher latency, unnecessary load on your backend auth services, or even invalidated tokens and failed network requests. I left a
Devil’s AdvocateThere’s a clear flaw with this tech test format: you’re largely testing for framework knowledge as opposed to skills. The classic adage goes; a good engineer can pick up a language or framework, so it’s unfair to test for their ability with a tool. This is absolutely true, however I’d argue it’s still the fairest format: if you were testing for performance optimisation, security skills, pure debugging, writing, or ARM assembly, you are biasing yourself towards candidates with a particular set of skills. Testing aptitude with the most popular Swift language features and UI framework is probably the closest you can get to a fair test to assesses suitability for a job using those features and frameworks. Furthermore, if you don’t bias a little bit towards a framework, you kneecap yourself since it’s harder to use a pre-made codebase. Forcing candidates to work “from scratch” subjects them to a ton of boilerplate, as well as making it hard to assess their aptitude for solving more interesting and difficult problems. ConclusionHiring is hard. Damn hard. I interviewed with Meta while job-hunting this year, and was interested to learn that they have the same technical bar from E3 (grad) to E6 (staff). Candidates get the same algorithmic exercises, and are held to the same pass/fail standard, with levelling is done entirely through measuring scope in other interviews. While this is neat, and perhaps necessary if you’re a massive corporation, you can do so much better than a cookie-cutter algorithmic interview if you have influence on your own process. Technical tests are the best way to check a candidate’s suitability for your job, but you need to think hard to get it right. The approach I’ve had the most success with is a live-coding exercise that has a candidate fix issues with a small codebase. You can test candidates on problems with various levels of complexity, allowing them to demonstrate aptitude, as well as problem solving, debugging, speed, and theoretical knowledge.
Happy hiring! Invite your friends and earn rewardsIf you enjoy Jacob’s Tech Tavern, share it with your friends and earn rewards when they subscribe. |








