Live coding interview for juniors: 20 minutes, think aloud
Live coding interview for juniors in 20 minutes: ask about the problem, walk through an example, code while talking, test edge cases. Runnable exercise inside.
A live coding interview is the part where you write code in front of the interviewer, usually for 20 to 45 minutes, and they want to hear you think. The trick for juniors is not to type right away. Ask about the problem, walk through an example by hand, then code. Do you go quiet when you're stuck? Let's watch Minh.
Thursday morning, the problem appears
In the previous post, Minh spent a week preparing for the technical interview at company A. Minh is a made-up character, a mix of situations familiar to new IT graduates, and company A is made up too. The interview has reached the coding part: the frontend lead shares an empty code editor and a 20-minute countdown timer, and says "please think out loud while you work".
The problem appears in four lines:
You get an array of expense notes, for example ["coffee", "lunch", "coffee"].
Write a function that returns the (at most) 3 notes that appear most often, with their counts.It's a small problem, close to the expense tracking product of company A, and it needs no clever algorithm. Minh thinks "easy", and that is exactly the most dangerous moment. Most people who fail an easy problem don't fail because they can't solve it. They fail because they solved it too fast and solved it wrong.
What is the interviewer really grading?
Before the code, I want to say something that changes how you prepare. The interviewer sitting there isn't only waiting for a function that works. They want to know what it would be like to work with you if you joined the team. Guides to coding interviews, such as Interview Cake's tips, tend to recommend the same things: say your thinking out loud, try concrete examples, and don't stay silent when you're stuck. I think the reason is easy to see. Correct code doesn't show them what you were thinking, while wrong code with a clear explanation shows them you can fix things.
For a junior, I think this matters even more. Nobody expects you to know everything, but they do expect you to ask in the right places and catch your own mistakes. So the best preparation isn't learning more algorithms, it's having a routine ready to run whenever a problem appears.
How do you split 20 minutes?
I split the 20 minutes into five parts. The numbers are only a suggestion so you can practice with a timer, and a real interview will drift a little:
Minutes 0–4 Understand the problem: restate it in your own words, ask about anything unclear
Minutes 4–7 Example and approach: walk one example by hand, explain your approach before coding
Minutes 7–15 Write the code, talking while you write
Minutes 15–18 Run the example, then try odd cases: empty, duplicates, ties
Minutes 18–20 Talk about the solution: how fast it runs, what you'd improve with more timeWhat I want you to notice is that the first four minutes contain no code. Many people see that as wasted time, and I understand the feeling when the clock is counting down. But those four minutes are the cheapest part of the whole interview. If you misread the problem, the ten minutes of code after it are ten minutes thrown away.
What do you ask about the problem?
Every problem has ambiguous spots, and interviewers often leave them there on purpose. Your questions show that you can see the ambiguity. For the problem above, Minh asks three groups of questions:
Minh: Do the notes "café" and "Café " count as the same note? One is capitalized and the other has a trailing space.
Interviewer: They count as the same.
Minh: If two notes appear the same number of times, which order should they be in?
Interviewer: Whichever appears first in the array goes first.
Minh: Can the array contain empty notes, and should I return the original text or the normalized text?
Interviewer: It can contain empty notes, skip them. Return the lowercase form with extra spaces removed.This takes about a minute, but it pins down four rules that Minh would otherwise have to guess. I write them as a comment at the top of the code file, because that's the specification of the exercise:
// Two notes that differ only in upper/lower case or extra spaces are ONE note;
// empty notes are ignored; when counts are equal, the note that appears first in the array comes first;
// the result shows the normalized form (lowercase, no extra spaces).You may notice the interviewer answered briefly. That's normal. They aren't hiding anything, and sometimes they haven't thought about the question themselves. If their problem really has something they haven't decided, they'll say "pick one and tell me why", which is also a good answer to hear.
What happens if you start typing right away?
To show why those four minutes are worth it, let me do the opposite. Suppose Minh skips the questions and types for 6 minutes. The approach everyone thinks of first is counting with an object, then sorting by count:
// The version written in a hurry in the first 6 minutes, before asking about the problem. Run: node practice/first-attempt.ts
function topNotes(notes: string[]) {
const counts: Record<string, number> = {};
for (const n of notes) counts[n] = (counts[n] ?? 0) + 1;
return Object.entries(counts)
.map(([note, count]) => ({ note, count }))
.sort((a, b) => b.count - a.count)
.slice(0, 3);
}
// The example the interviewer gave.
console.log(topNotes(["café", "Café ", "lunch", "café", "pho", "lunch", " "]));The code is short, reads cleanly, and at a glance looks right. The example array has seven elements, including "Café " with a capital letter and a trailing space, and an empty note. I run it with Node.js 22.18 or later, with nothing to install:
[
{ note: 'café', count: 2 },
{ note: 'lunch', count: 2 },
{ note: 'Café ', count: 1 }
]The result is wrong. "café" appears three times if you count "Café ", but the code counts only 2. "Café " sneaks in as a separate note. And "pho", a real note, is pushed out of the top 3. All of it is wrong because the code compares strings exactly as written, which is the very thing the first question settled.
What's worth noting is that Minh could have caught this without running the code. Walking through the example by hand in minutes 4 to 7, saying each element out loud, is enough: "the first element is café, count one. The second is Café with a capital, which I agreed is the same note..." The phrase "I agreed" exposes that the code doesn't handle it. Walking through an example by hand is the step I'd never skip, because it's cheaper than finishing the code and debugging in front of people.
The fixed version, after asking about the problem
The fix has two parts. I normalize each note before counting, using a small function, and I use a Map instead of an object so insertion order is preserved:
// 20-minute exercise: given a list of expense notes, return the (at most) 3 notes that appear most often.
// Rules (after asking about the problem): two notes that differ only in upper/lower case or extra spaces are ONE note;
// empty notes are ignored; when counts are equal, the note that appears first in the array comes first;
// the result shows the normalized form (lowercase, no extra spaces).
export type TopRow = { note: string; count: number };
const normalize = (note: string) => note.normalize("NFC").trim().replace(/\s+/g, " ").toLowerCase();
export function topNotes(notes: string[], limit = 3): TopRow[] {
const counts = new Map<string, number>(); // a Map keeps insertion order, so "appears first" is preserved
for (const n of notes) {
const key = normalize(n);
if (key === "") continue;
counts.set(key, (counts.get(key) ?? 0) + 1);
}
// JavaScript's sort is stable (since ES2019): equal counts keep their insertion order.
return [...counts]
.map(([note, count]) => ({ note, count }))
.sort((a, b) => b.count - a.count)
.slice(0, limit);
}Each step in normalize solves something specific. trim removes spaces at both ends, replace(/\s+/g, " ") collapses runs of spaces in the middle into one, and toLowerCase makes everything lowercase. The first step, normalize("NFC"), looks unnecessary, and I'll explain just below why it isn't.
The two comments in the code are the two places Minh should say out loud while writing, because they're the two decisions an interviewer wants reasons for. First, a Map keeps the order in which items were added, so "the note that appears first comes first" holds without extra code. Second, JavaScript's sort has been stable since ES2019, which means two elements with equal counts keep their order from before the sort. If Minh doesn't remember this, another safe way is to also compare the position of each note's first appearance, and to say plainly that it's the belt-and-braces approach.
Run the example, then try the odd cases
Minutes 15 to 18 are for checking. Minh runs the interviewer's example first, then thinks up edge cases and says each one aloud: an empty array, two notes tied, fewer than three different notes, more than three, and an empty note. I wrote those cases as tests with Node.js's built-in test runner:
// Run: node practice/top-notes.test.ts (Node.js 22.18+, nothing to install)
import assert from "node:assert/strict";
import { test } from "node:test";
import { topNotes } from "./top-notes.ts";
test("the interviewer's example", () => {
const result = topNotes(["café", "Café ", "lunch", "café", "pho", "lunch", " "]);
assert.deepEqual(result, [
{ note: "café", count: 3 },
{ note: "lunch", count: 2 },
{ note: "pho", count: 1 },
]);
});
test("an empty list gives an empty result", () => {
assert.deepEqual(topNotes([]), []);
});
test("empty notes, or notes with only spaces, are ignored", () => {
assert.deepEqual(topNotes(["", " "]), []);
});
test("when counts are equal, the note that appears first comes first", () => {
assert.deepEqual(topNotes(["milk tea", "coffee", "coffee", "milk tea"]).map((row) => row.note), ["milk tea", "coffee"]);
});
test("fewer than 3 different notes returns exactly that many", () => {
assert.equal(topNotes(["coffee", "coffee"]).length, 1);
});
test("an accent typed in decomposed form (NFD) is still the same note", () => {
const nfd = "café".normalize("NFD");
assert.deepEqual(topNotes([nfd, "café"]), [{ note: "café", count: 2 }]);
});
test("with more than 3 different notes, only 3 are returned", () => {
assert.equal(topNotes(["a", "b", "c", "d"]).length, 3);
});# tests 7
# pass 7
# fail 0In a real interview, Minh won't have time to write a test file. Minh would run the example in the editor's console and read the result aloud. I wrote tests here so you can practice on your own, and so you can see that each rule has its own check.
The sixth test may make you ask why I test an accent in "decomposed" form. The letter "é" has two representations in a computer. The NFC form is a single character, while the NFD form is the letter "e" plus a separate combining accent. The two strings look identical on screen, but comparing them with === says they differ. In my run, "café" in NFC has length 4 and in NFD has length 5. Some phone keyboards and some data sources produce NFD, so the normalize("NFC") line protects you from a bug that's almost impossible to see by eye. (Vietnamese makes this much more common, since nearly every vowel carries accents.)
To make sure the tests do their job, I tried removing each rule from normalize and running again. Removing the line that skips empty notes turns one test red. Removing normalize("NFC") turns one red, removing trim() turns two red, and removing toLowerCase() turns one red. No rule is spare.
What do you say when you're stuck?
Everyone gets stuck at some point, and silence is the worst way to handle it. The interviewer can't read your mind, so a minute of silence looks like you gave up. I suggest having a few sentences ready for those moments:
When the problem is unclear: "Let me ask a quick question: ..."
When you need to think: "Give me a minute to think about the approach."
When you're stuck: "I'm stuck on ... I'll try two directions: ..."
When you forget syntax: "I don't remember the exact name of this function. I plan to use one that ..., may I look it up quickly?"
When you spot a bug: "I see something wrong here, because ... Here's how I'd fix it.""May I look it up quickly?" may sound strange, but it reflects real work. Every developer looks up documentation every day. There's no general rule about whether you're allowed to, and each company decides differently, so asking first is the safe route. What's more worrying is pretending to remember and writing the wrong function name.
And when you find your own mistake, as Minh did in minute 15, say so right away. In my opinion, someone who catches their own bug and explains why is more trustworthy than someone who writes correct code but can't say how it works.
The last two minutes: talk about the solution
With about two minutes left, Minh doesn't need to write more code. This is the time to talk about what was done, and interviewers often ask: "how fast does this function run?" For this problem, a short answer is: the function walks through the array once to count, and sorts the list of different notes. With *n* notes, counting takes time proportional to *n*, and sorting takes time proportional to the number of different notes times its logarithm. Minh doesn't need the exact formula. Naming the two parts, "one pass" and "a sort", is enough.
Minh can also say what they'd do with more time, like removing accents when comparing so that "cafe" and "café" merge into one, or taking the number of rows to return as a parameter, which already exists as limit = 3. Showing that you know what you haven't done shows you see the bigger picture, and that's what people look for in a junior.
How do you practice so you don't freeze in the interview room?
The most important part of this post is really outside the interview. This routine only becomes smooth after you've run it a few times, and the most effective way I know to practice is to set yourself a problem, start a timer and talk out loud alone. Coding while speaking is a different skill from coding in silence, and it feels awkward the first time. After a few sessions it becomes a habit.
Each time, pick a small problem, set a 20-minute timer and go through the five parts above. At the end, look back at two questions: did you ask about the problem, and did you walk through an example by hand before typing? Those two habits are worth more than ten extra algorithm problems.
What comes after the technical round?
Minh leaves the technical interview feeling reasonably good. The function ran correctly on the example, three edge cases were spoken aloud, and Minh fixed a small bug in minute 16. A few days later, company A messages back: Minh is invited to the next round, a conversation with the human resources team.
This round has no countdown and no code editor. The person across the table will ask things like "tell me about a time you had a conflict while working in a team" or "what's your biggest weakness". These questions have no correct answer you could check with a test, and many technically strong candidates stumble here. How to answer a behavioral question with structure, without sounding like you're reciting, is the job of later posts.
You can follow the whole journey in the series The junior journey. If you want more practice, the question tell me about yourself in one minute and others are in the interview question bank.