53 questions
Interview questions
Practise answering at the level you are applying for. Each question has a short answer and what the interviewer really wants to hear.
- Junior 0–2 years: fundamentals, everyday tools, working safely in a team.
- Middle 2–5 years: owning a feature end to end, understanding trade-offs.
- Senior 5+ years: system design, technical leadership, long-term decisions.
AI writes code that calls a library function you have never seen. How do you check that it really exists?
Open this question on its own pageThe interviewer wants to know whether you treat AI-written code as a draft that needs verifying, and whether you know cheap, fast and reliable ways to verify it, rather than just asking the AI again.
The most reliable way is to use a source the AI cannot make up. You look for the function name in the code or the type files of the library you have installed, for example by searching inside
node_modules. If you can't find it, the function doesn't exist in the version you use. Asking the AI "does this function exist?" is a weak method, because it can answer confidently in exactly the same way it invented the function at first.There are three layers of verification, from cheap to thorough:
- Run it. This catches errors on the path the program takes, but you only see errors one at a time, and an error in a branch that rarely runs will sleep until a real user hits it.
- The compiler or a linter. With TypeScript,
tsc --noEmitreads the whole file and reports every call with the wrong shape in a single pass. One error at the top of a chain can cause follow-on errors, so fix the first one and run it again. - Tests. These catch behavior that you thought of checking, for example "editing this person's message doesn't touch the other person's ledger".
An important point to make: none of the three layers catches code that has the right shape but the wrong meaning. A function that does exist but is used wrongly, or one that searches shared data instead of each person's own data, passes all of them. So in the end you need to read the official documentation for things like "within what scope is this ID unique", and write tests yourself for the situations that matter.
Bonus points: say that you treat every unfamiliar function the AI offers as a question to answer, not as an answer, and mention that made-up function names are often borrowed from a similar library (like a function from Telegraf showing up in grammY).
How can you tell whether a unit test written by AI really does anything?
Open this question on its own pageThe interviewer wants to know whether you treat tests as something that needs checking, or feel safe as soon as you see green. A good answer shows that you understand a test is valuable when it goes red while the code is wrong.
The main idea: a test that is green on correct code proves nothing yet. The way to check is to break the code on purpose. You change one small thing, such as flipping a condition, removing a line or changing a constant, and run the tests again. If they stay green, the test doesn't protect that spot. This technique is called mutation testing, and there are tools that do it automatically, such as Stryker for JavaScript.
You can add four habits:
- Say what needs protecting when you ask AI to write tests, for example "two users never share data", instead of just "write tests for this file".
- Don't trust coverage on its own. Coverage only tells you which lines of code ran, not whether the result was checked. Two test suites can both have 100% coverage while one catches far fewer bugs.
- Read every assert. Checks like "it responded" or "it contains this word" are very loose. Comparing against the full expected result is much stricter.
- Try exactly the bugs that have happened before. Every bug you have hit should have at least one test that goes red if it comes back.
Bonus points: give a concrete example, such as a bot that has tests for its expense-logging feature but only tried one user, so when the code accidentally used a single shared list for everyone, the tests stayed green. Only after adding a test with two users did the bug show up.
What is RAG, and when should you use it instead of sending the whole document in the prompt?
Open this question on its own pageThe interviewer wants to know whether you understand RAG as a process with steps, not just a fashionable name, and whether you can weigh the trade-offs.
RAG, short for retrieval-augmented generation, has three steps. One: split the documents into small passages, done once in advance. Two: when a question comes in, find the few most relevant passages, either by counting keywords or by comparing meaning. Three: put those passages into the prompt and only then call the model, with an instruction to answer based only on those passages.
Use RAG when:
- The documents are long enough that sending all of them is expensive. Everything in the prompt is sent again on every call, so cost and latency grow with the length of the document, while RAG sends only a few passages.
- The documents change often. You edit the document and update the index, instead of rewriting the prompt.
- You need to know what the answer is based on. Each passage has a name, so you can make the model cite its source and check it afterwards.
Don't use RAG when the document is only a few pages. Sending the whole document is simpler and removes one place that can go wrong.
An important point to make: RAG adds one more place for errors, the search step. If that step doesn't bring back the right passages, the model may answer incompletely or guess, while the answer still sounds perfectly fluent. So when RAG answers wrongly, the first thing to do is look at which passages were sent, not to edit the prompt right away.
Bonus points: give a concrete example, such as a customer asking "how long until I get my money back" while the document says "refund period". Keyword search misses this because the two phrasings share no words, and that is why people move to searching by meaning with embeddings.
How is an AI agent different from a chatbot and a workflow? When should you not use an agent?
Open this question on its own pageA good answer tells the three apart with one question: who decides the next step?
- Rule-based chatbot: hand-written rules decide (keyword matching, for example). Cheap, but it doesn't understand sentences; "has my order been delivered?" can be read as a question about the delivery fee because it contains "deliver".
- Workflow: a developer writes the path in code in advance; the LLM only does small steps such as classifying or writing the reply. Predictable, easy to test and cheap, but it can only follow the paths that were written.
- Agent: the LLM chooses the next step itself, in a loop: call a tool, read the result, decide whether to call another tool or answer. It can handle multi-step, conditional requests such as "if my order hasn't gone out yet, add one more item".
Don't use an agent when:
- the kinds of request are known up front and each has a clear way to handle it (a workflow is enough, faster and cheaper);
- a single LLM call with the right information already solves it;
- the action is high-risk (money, deleting data) and no person reviews it.
Bonus points: mention a limit on the number of loop steps, the fact that every step costs a model call, and a combined design, with a workflow at the front door and the agent only for complex requests.
What is LLM hallucination? When you build a customer service chatbot, how do you reduce it?
Open this question on its own pageHallucination is when a language model produces information that sounds plausible but is wrong, or isn't in the data it was given. For example, a customer asks "has order 1041 been delivered?", the request sent to the model contains no order data at all, and the model still answers "yes, your order has been delivered!".
The key point to get across is that the model only knows what is in the request plus the general knowledge it learned in training. It doesn't connect to your database by itself. So for data that keeps changing, such as an order's status, writing a better prompt is not the fix.
Ways to reduce it, from easiest to most reliable:
- Put the information it needs into the prompt (opening hours, prices) and tell it to "only use this information".
- Allow the model to say "I don't know", along with a concrete replacement answer. Anthropic's documentation lists this as the first basic technique.
- For dynamic data, let your code look up the real data and hand the result to the model, using tool calling or RAG.
- Add a code check for risky answers, with tests for that check.
Bonus points: say clearly that these techniques reduce hallucinations rather than eliminate them, so high-risk actions (money, promises to customers) still need a person to approve them.
What is tool calling (function calling)? When you use it, does the model run your functions itself?
Open this question on its own pageTool calling is how an LLM gets to use data and actions outside itself. The most important point to make is that the model never runs any function itself: it only writes a request, and your code decides whether to run it.
One tool call goes through four steps:
- The application sends a request with a list of tools. Each tool has a name, a description (including when it should be used) and a JSON Schema for its input.
- If it needs a tool, the model returns a
tool_useblock (the tool name and arguments) withstop_reasonset totool_use, instead of an answer. - The application runs the real function, for example reading order 1041 from the database, and sends the result back in a
tool_resultblock with the sameid. - The model reads the result and writes the final answer.
It's worth adding a few points that show you've actually built one:
- Input written by the model is untrusted data, and you check it the way you'd check data a user submits (format, permissions). The schema only guarantees the types, not that the values make sense.
- Tool errors should go back to the model (for example
is_error: truewith "there is no order 1099") so it can ask the customer again, instead of crashing the whole application. - Every tool use costs an extra API call, and the tool definitions count as input tokens in every request.
- When a question needs several steps (find the order number by phone number, then look up its status), the application has to keep looping until the model answers in text. That's the agent loop.
How do you choose an AI coding tool (Copilot, Cursor, Claude Code…)? What criteria do you use?
Open this question on its own pageA good answer doesn't rank the tools. It shows that you understand how they differ and how you stay in control of the result.
The first criterion is how much work you hand over. Suggestions as you type (Copilot, Cursor's Tab) keep you writing every line. An agent inside the editor edits several files and lets you review each change. An agent that runs commands, like Claude Code, runs the tests, reads the errors and keeps fixing. The more you hand over, the more carefully you have to review.
The second criterion is your company's rules: which tools are allowed, whether code may leave the company, and what data policies apply. At a company, this is usually the deciding factor.
The third criterion is cost and free plans; prices change constantly, so always check the official pricing page.
The most important part is how you check the result, whichever tool you use:
- write a short spec and tests before handing the task over;
- say explicitly "don't change the tests";
- run the tests yourself under the same conditions as production (for example, the UTC time zone);
- try a few cases beyond the tests, and be able to explain every part of the code before merging.
Bonus points: describe a specific bug that your tests, or your own extra checks, caught.
How do you debug a hard bug? At which step do you use AI?
Open this question on its own pageThe interviewer wants to hear a process, not a tool. A good answer has clear steps and shows that you verify before you believe.
A process you can describe:
- Reproduce the bug in the smallest way possible: one command, one script, one test. If you can't reproduce it yet, you shouldn't fix it yet, because you have no way to know you are done.
- Write down the actual and the expected result. "The total is wrong" is not enough; "the total comes out as 67,000 VND when it should be 35,000" is.
- Follow the data from where it enters to where it goes wrong: where it is created, which functions it passes through, who shares it. Read the stack trace if there is one, and set a breakpoint instead of sprinkling
console.log. - Rule things out with evidence: which parts already have green tests, which parts you checked by hand.
- Fix it, then rerun the reproduction and the whole test suite. Find every place that calls the function you just changed.
About AI, say clearly where you use it and where you don't. AI helps a lot at step 3: reading unfamiliar code, suggesting suspicious spots, explaining an error message. But AI sees only what you give it, so you give it the reproduction, the actual result, the expected result, the files the data passes through and what you have already ruled out. You also ask it to point to the file and line before it proposes a fix, so you can check the cause yourself.
Bonus points: tell about one specific bug, for example one that only appeared when two users acted at the same time, and say what you did so that it can't come back (a test that reproduces exactly that bug).
How do you use AI to review code, and how do you check its comments?
Open this question on its own pageThe interviewer wants to know whether you use AI as a controlled second reviewer rather than a judge, and whether you keep your own ability to read code.
I review the code myself first, and only then ask the AI. If I look at the AI's list first, I get anchored to it and only judge what it brings up, instead of searching on my own. When I review by myself, I use the familiar frame: design, functionality and edge cases, complexity, tests, naming (Google has a guide on what to look at in a review).
When I ask the AI, I require every comment to come with the relevant line, a specific input that causes the problem, and a severity, so there is something I can run. Then I check each comment: I write a small probe, or run that input, and see whether the bug is real. This step matters because AI can be wrong while sounding very sure, for example claiming that a function will throw an error when it quietly returns a wrong value. Environment-dependent bugs, like time zones, need to be tried in more than one configuration.
Finally I keep a record of every review: what I caught first, what only the AI caught, and what the AI got wrong. The "only the AI caught" column tells me which kinds of bugs I am weak at.
Bonus points: mention that AI lacks the context for design questions (should we add this feature right now?), so treat its answers on design as opinions, and that you never paste API keys or customer data into a prompt.
How do you use AI tools when you code? How do you know the code the AI wrote is correct?
Open this question on its own pageThe interviewer isn't asking whether you use AI; they want to know whether you stay in control of the code you submit. A good answer describes a concrete process:
- Define "correct" before asking the AI. Write a short requirement with a few example inputs and expected results, including the error cases. For example: the message "coffee 35k" must give 35,000 đồng, and a message with no amount must be rejected.
- Turn the examples into tests, let the AI write code until the tests pass, and run the tests again yourself.
- Read and be able to explain every part of the code before committing. Anything you can't explain, ask about again or rewrite.
- Try cases beyond the examples. Tests only protect what you thought of; "35 thousand" might be read as 35 đồng while every test stays green.
- Check APIs and libraries against the official documentation, because AI can call functions that don't exist or use outdated syntax.
- Never paste secrets (API keys, customer data, company code you aren't allowed to share) into an AI tool.
Bonus points: tell a specific story of when you caught AI-written code being wrong, and how you caught it.
If you ask AI to write a commit message from a git diff, what is missing, and how do you check the result?
Open this question on its own pageThe interviewer wants to know that you use AI as a controlled helper, and that you understand what a good commit message should contain.
What AI is missing is the reason. A diff only shows which lines were added or removed, that is, "what changed". The "why" lives in your head: which bug users hit, why you chose this approach over another. If you only give it the diff, you often get a message like "add bot.catch to handle errors", which is literally correct but tells a later reader nothing about what happened. That is why I always add a sentence or two about the intent to the prompt.
I check the result in three steps. First, read each sentence against the diff: if the message says "added tests" and the diff has no test file, that is an invention and it has to go. Second, check whether the first line has the project's format (for example
type(scope): description, short, starting with a verb). Third, check whether the body explains why instead of retelling each line of code.One more point: AI writes well only when the diff contains a single piece of work. If you pile a spec, a feature and tests into one commit, no message can describe it well. Split the commits by meaning first (
git add -phelps you pick individual hunks), and only then ask AI.Bonus points: mention that the person responsible for what a commit says is you, not the tool, and that in a pull request the "how I tested it" section has to be filled in by you from real results.
When you ask AI to write or fix code, how do you write the prompt so the result is usable?
Open this question on its own pageThe interviewer isn't looking for a magic phrase. They want to know whether you stay in control or just paste a request and accept whatever code comes back. A good answer names three things every prompt should make clear:
- What to do, with enough context: which files, which spec, the conditions it runs under (for example, tests run with
TZ=UTC). - What not to do: "don't write code yet, ask about anything unclear", "don't change the tests", "don't add libraries", "fix only issue number 1". Without constraints, AI tends to take the shortest path, like editing a test until it's green.
- How the result will be checked: "run the tests and paste the output", "the new tests must fail on the current code", "for every claim, give me a command I can run to verify it".
Tell a concrete example. For instance: before changing a command in an app, you asked AI to list the inputs most likely to go wrong, ran them yourself, and found that the message "/total month 10" was recorded as an expense of 10 đồng (Vietnam's currency). Then you wrote failing tests first, and only then asked AI to fix the code without touching the tests.
Bonus points: say that the checking step is the part you don't hand over to AI. An explanation that sounds reasonable isn't necessarily right, so you run the commands, measure, read the diff, and only then accept it.
- What to do, with enough context: which files, which spec, the conditions it runs under (for example, tests run with
When you have to use a library or take over a codebase you have never seen, how do you learn it?
Open this question on its own pageThe interviewer wants to know whether you have a method or just try things until they work. A good answer goes from broad to narrow:
- Find the version and the entry point. Look at which version
package.jsonuses, read the README and the entry file (for exampleindexormod). With a codebase, get the project and its tests running before you read deeply. - Follow one concrete flow instead of reading everything. Pick exactly the job you need to do (for example "receive a message and reply") and trace it from the public function inward.
- List your assumptions and check each one. With a Telegram library, for example: is the
datefield in seconds or milliseconds, does an edited message go through the handler, what happens when a handler throws. Every answer should point to a file and line in the source or in the official documentation. - Write a small script you can run to see the real behavior, ideally one that needs no network or outside service.
If you use AI, say exactly how: let it read the source of the exact version installed (in
node_modules) instead of answering from memory, ask it to include the file and line number, and then open and read those yourself. The model's memory may be right for an older version, so a fluent answer can still be wrong for the version you have.It is good to tell a real example, such as the time a wrong assumption about the time unit produced a date in January 1970, and you found it by running the code rather than by reading about it.
Bonus points: say that you write down what you learned (which file does what, where it is easy to go wrong) in the README or in notes, so the next person doesn't have to trace it again.
- Find the version and the entry point. Look at which version
If you get the job, what will you do in your first week?
Open this question on its own pageThe interviewer wants to know whether you have a realistic plan for the early period, whether you put learning how the team works first, and whether you avoid promising things that aren't possible.
A good answer breaks down into a few specific goals, instead of saying something general like "I will learn a lot."
First, get the project running on your own machine within the first few days, and write down the places where the setup instructions are missing or wrong. A newcomer sees the snags that long-time members no longer notice, and fixing them helps the whole team.
Second, understand how the team works: the branch naming convention, how many people need to approve a pull request, which automated checks must be green, who reviews. A quick way is to read recently closed pull requests.
Third, take a first small task, such as fixing documentation or a small bug that has already been picked out, to learn the process without the pressure of a hard problem.
Fourth, ask your mentor for a mid-point feedback session, and ask what they expect from you at each milestone of the probation period. You can also say that you keep a short end-of-day journal to note what you didn't understand and what you tried before asking.
Bonus points: mention that probation is counted in calendar days but contains fewer working days, so goals need to be the right size, and that you don't need to know the whole codebase in your first week.
What is a behavioral question, and how does the STAR method help you answer it?
Open this question on its own pageThe interviewer wants to know whether you can tell a real story with structure and say clearly what you yourself did, instead of answering in generalities.
A behavioral question is one of the form "tell me about a time when…": you ran into a conflict in a team, hit a hard bug, made a mistake. The interviewer uses your past behavior to predict how you will work in the future. There is no right answer you could check with a test, so how you tell the story decides the impression.
STAR is a four-part frame for telling it:
- Situation: a short background, enough to understand what was happening.
- Task: your responsibility in the situation, that is, why this was your job.
- Action: the specific steps you took, using "I" rather than "we", because the listener is evaluating you.
- Result: what happened afterwards, ideally measurable with numbers, plus what you took away from it.
Common mistakes are spending too long on the background and too little on the Action, talking about the whole team instead of your own part, and forgetting the Result or giving a vague one like "things got better." A good way to prepare is to have about three real stories ready, each written as four STAR lines, and then pick the one closest to the question instead of thinking up a new one.
An important point to make: don't memorize the wording, and don't invent numbers. You only need to remember the four lines of each story, tell them differently each time, and use only numbers you actually have.
Bonus points: mention that for questions about weaknesses or mistakes, you tell one real thing together with what you did to fix it, instead of a "weakness" that sounds safe, like "I'm too much of a perfectionist."
What salary are you expecting, and how do you respond when the offer is lower than you hoped?
Open this question on its own pageThe interviewer wants to know whether you prepared, whether you understand your own value, and whether you can talk about salary calmly, without dodging the subject and without making unreasonable demands.
Before you answer, you need to do your homework. If the job ad already states a salary range, that is the best data you can get: you know the range the company is willing to pay. If it doesn't, look at common levels for your role, seniority and location in salary reports and similar job ads. Companies usually build pay in ranges and bands, so there is a ceiling the recruiter can't go above.
When you are asked, give a range with a basis, tied to what you bring (projects, tests, skills that match the requirements), instead of a bare number or a personal reason like rent. If you don't want to name a number yet, you can ask for the salary range of the position first and then answer within it.
When you get an offer that is lower than you hoped, don't accept or reject it on the call. Say thank you, say clearly that you are interested, and ask for the offer in writing and a day or two to think. Read carefully items such as the base salary, the percentage of salary during probation, the end date of probation, the salary that insurance contributions are based on, bonuses and the date of the next salary review, then reply by email, politely, with a specific proposal and your reasons. In Vietnam, the probation salary must be at least 85% of the salary for that job under the 2019 Labor Code.
If the company can't raise the salary, you can ask about other things such as a salary review date in writing, a learning plan or the number of work-from-home days.
Bonus points: mention that comparing the options by total first-year income shows which items are worth asking for, and that polite negotiation is usually a normal part of hiring.
What is a database migration? What should you watch out for when adding a NOT NULL column to a table that already has data?
Open this question on its own pageA migration is a file (usually SQL) that describes one change to the schema, such as creating a table, adding a column or adding an index. The files are named in time order, committed to Git and run one after another, so the databases on dev machines, staging and production all go through exactly the same steps. It also means schema changes get reviewed in pull requests, just like code.
For the NOT NULL part, the interviewer wants to hear two things.
First,
alter table posts add column tags text[] not nullfails on a table that has data: the existing rows have no value for the new column, which meansnull, and that violates the constraint (PostgreSQL reportscolumn "tags" of relation "posts" contains null values). On a dev machine with an empty table it runs fine, so the error only shows up in production. The fix is a default value:not null default '{}'. If there's no sensible default, split it into steps: add the column as nullable, fill in the data, then setnot null.Second, backward compatibility. During a deploy, the migration and the new code don't switch over at the same instant. There's usually a window where the old code runs against the new schema, so a migration must not break the old version: don't drop or rename columns the old code still uses, and give new columns a default so the old
insertstatements keep working.Bonus points: say that a migration that has already run in production is never edited; if you need to change something, you write a new migration.
Why don't we usually store images directly in the database? Where do you store them?
Open this question on its own pageImages are large and you almost never query them by their content. Storing them directly in the database (as binary or base64) causes several problems:
- It uses the most expensive space. Database storage is usually limited and costs more than file storage. For example, Supabase's free plan has a 500 MB database: a long post is only a few dozen KB of text, while a single phone photo is several MB. A few dozen posts with images and it's full.
- It's slow and heavy. Every query drags large data along, backups and restores take longer, and the database's cache fills up with image bytes.
- It can't use a CDN. Images in object storage are served through a CDN and cached close to users; images in the database have to go through the application server.
The common approach: store image files in object storage (Supabase Storage, Amazon S3, Cloudflare R2…) and keep only the path and some metadata (size, alt text, who uploaded it) in the database. On top of that, compress images before uploading (resize, convert to WebP/AVIF) to cut both storage and bandwidth.
Bonus points: mention cleaning up images that no post uses anymore, and permissions for the file store, meaning who can upload and who can view.
What is the difference between git merge and git rebase? When should you use each?
Open this question on its own pageBoth bring changes from one branch into another, but they record history in two different ways.
git mergecreates a merge commit that joins the two branches. History stays as it happened, and you can see exactly when the branch split off and came back.git rebaserewrites your branch's commits on top of the target branch. History becomes a straight line that is easier to read, but the commits get new hashes.
git switch feat/login git fetch origin git rebase origin/main # straight history; only do this on a branch that is yours aloneIn an interview, mentioning the trade-off (true history versus readable history) and the rule your team actually follows earns more points than the definitions alone.
When you get a code review comment you disagree with, what do you do?
Open this question on its own pageInterviewers ask this to see how you work in a team, not to see whether you win arguments. A good answer shows that you can separate feelings from technical facts and that you decide based on evidence.
You can answer in four steps:
- Make sure you understand the comment first. Read it again, and if it is unclear, ask the reviewer what worries them. Many times I disagree only because I misread what they meant.
- Try what they suggest. Run the change and run the tests. The result often answers the question for you: either the comment is right and you follow it, or it breaks a specific case.
- Reply with data and end with a question. For example: "I tried this and 4 of the rejection tests went red, because '35,000' gets read as 35000 without any error. I'd like to keep the current approach. Does that sound okay to you?" The open question leaves room for the reviewer to know something you don't.
- If you are not done after two rounds, talk in person. Ten back-and-forth comments cost more time than a five-minute call. If you still can't agree, follow the team's convention or ask a third person.
Two more points are worth making. First, tell real bugs apart from personal taste: for taste, explain your reasoning but be ready to follow the team's way. Second, if a comment is right but out of scope, write it down as a separate task instead of stuffing it into the pull request under review.
Bonus points: describe one specific time you disagreed, what you tried and how it ended, even if the reviewer turned out to be right.
Why shouldn't you commit straight to the main branch? What does a good pull request need?
Open this question on its own pageCommitting straight to
mainmeans the change becomes shared code immediately, before anyone has read it and before the automated checks (CI) have run. Ifmainis connected to deployment, a small bug goes straight to production. When you work on a separate branch,mainalways stays in a working state, several people can work in parallel without stepping on each other, and every change has a place for discussion.For the second part, the interviewer wants to hear you think about the reviewer. A good pull request usually has:
- One single purpose. Fix one bug or add one feature. A PR that mixes a bug fix with a bulk rename and a library upgrade is hard to review, and hard to pull apart when something goes wrong.
- Commits split by meaning, with clear names. For example one commit for the fix and one for the test. Generated files such as
package-lock.jsongo in the same commit as the change that produced them. - A description that answers four questions: what the problem is, what changed, how you checked it (with the specific commands) and what you deliberately did not do.
- A self-review done first. Read your own diff to remove a forgotten
console.log, personal config files and experimental code. - A reasonable size. A few dozen to a few hundred meaningful lines. A thousand-line PR often gets a single "LGTM" that nobody really read.
It helps to tell an example of your own, such as the time you reread your diff and caught a log line printing a customer's phone number before you opened the PR.
Bonus points: say that many teams turn on branch protection to block direct pushes to
mainand require green CI before merging, and that small PRs make it faster to find the commit that caused a bug.What's the difference between GET and POST? Why shouldn't you use GET to delete data?
Open this question on its own pageGET is for reading data: its parameters live in the URL, and it can be cached, saved in history and called again automatically by browsers or scanning tools (prefetching, link previews). POST is for sending data that changes state, and the data lives in the request body.
GET must be safe (it changes nothing) and idempotent (calling it many times has the same effect as calling it once). If a GET link deletes data, a single bot scanning links or a browser prefetching is enough to lose that data.
In a live coding interview, what do you do before you write the first line of code?
Open this question on its own pageInterviewers ask this to see whether you have a process or just type out of reflex. A good answer shows that you spend the first few minutes understanding the task, because that is the cheapest part of the whole session.
You can answer in three steps:
- Restate the task in your own words and ask about the unclear parts. For example, with a task like "count the note that appears most often": "Do uppercase and lowercase count as the same? If two notes appear the same number of times, how do I order them? Can the array be empty or contain blank entries?" Your questions show that you can spot where the task is unclear, and the answers pin down the rules of the problem.
- Walk through one example by hand, talking as you go. This often reveals a rule you haven't handled, before you have written a single line of code.
- Say the approach before you write it. One or two sentences, for example "I'll normalize each note, count with a Map and then sort in descending order." If that direction is off, the interviewer has a chance to steer you before you lose ten minutes.
It is worth adding the reason: if you misunderstand the task, all the code after it is wasted effort, and the interviewer is judging how you think, not only the result. If you get stuck, say where you are stuck and what you are trying, instead of going silent.
Bonus points: tell about a bug that the walk-through by hand helped you catch early, for example code that compared strings as written, so "Cà phê " and "cà phê" were counted as two different notes.
How is a breakpoint different from console.log when debugging? When do you still use console.log?
Open this question on its own pageA breakpoint pauses the program on one line and lets you see the value and type of every variable, see who called the function (the call stack), and type more expressions that are evaluated right in that context.
console.logonly prints what you guessed in advance you'd need.One example shows the difference: a menu price was stored by mistake as the string
"35.000"(thirty-five thousand, written with a dot between the thousands as in Vietnam and much of Europe).console.log(item.price)prints35.000, which looks like a perfectly normal price. In the debugger's Variables panel the value shows up with quotes, andtypeofreturns'string'. The bug is obvious at once:"35.000" * 2is 70, because JavaScript reads the dot as a decimal point.Tools worth mentioning: conditional breakpoints (only stop when
order.id === 1044), Watch, the Debug Console, the Call Stack, and logpoints (log without pausing and without editing the code).console.logstill makes sense:- in a small script you run once;
- for timing bugs, where pausing the program changes the order things run in;
- in production, where you can't attach a debugger, and there you should write structured logs.
Bonus points: logging
JSON.stringify(value)or{ value }makes it clear whether you have a string or a number.What is a stack trace? When you see "Cannot read properties of undefined", how do you find the cause?
Open this question on its own pageA stack trace is the list of function calls that were still in progress when an error happened, with the file name, line and column of each call. In JavaScript, the top frame is where the error happened; the frames below it are the path that led there.
For
TypeError: Cannot read properties of undefined (reading 'name'), a good answer goes through these steps:- Read the message: the property being read is
name, so whatever comes right before.nameisundefined. - Find the first frame from your own code: skip frames in
node_modulesornode:internal, then open that exact file, line and column. - Trace the data back: the line that threw is where the error showed up, not necessarily where it started. Walk down the frames to see where the
undefinedvalue came from: an API that hasn't returned yet, empty initial state, or data that is missing a field. - Reproduce it with the smallest possible example, then fix it in the right place: fix the data at its source, or handle a legitimate missing value with a default or
?..
Bonus points if you add that when you catch an error and throw a new one,
new Error("…", { cause: err })keeps the original error, and that minified production code needs source maps before its stack traces are readable.- Read the message: the property being read is
What's the difference between var, let and const in JavaScript?
Open this question on its own pagevaris function-scoped, is hoisted (the declaration is moved to the top of the function with the valueundefined) and can be declared again.letis block-scoped{ }, can't be declared again in the same block, and can't be used before the line that declares it.constis likeletbut can't be reassigned. Note: an object or array declared withconstcan still be changed inside.
const user = { name: "Alex" }; user.name = "Sam"; // fine user = {}; // TypeErrorA good habit: use
constby default, useletonly when you really need to reassign, and avoidvarin new code.In the Next.js App Router, how is a route group (folder) different from a private _folder? When do you use each?
Open this question on its own pageBoth are folder naming conventions inside
app, but they solve two different problems.A route group is a folder whose name is in parentheses, like
(site)or(shop). Next.js uses it to group pages but leaves the group name out of the URL:app/(site)/posts/page.tsxis still/posts. Its main use is giving a set of pages their own shared layout. For example, a blog's public pages need a header and footer and are generated ahead of time, while the admin pages need their own frame and are built fresh on every visit. Put the public pages in a(site)group with its own layout and the two sides stay separate without the URLs changing.A private folder has a name starting with an underscore, like
_components. Next.js ignores that folder and all its subfolders when building routes, so it's a place to keep non-page code (components, helpers) right next to a page.A common follow-up: even without the underscore, a file in
appdoesn't become a page unless it's apageorroutefile. So_folderis mainly about making the intent clear and avoiding clashes with Next.js's special file names.Bonus points: mention that two layouts can apply to the same
/(the root layout and the group's layout) while wrapping different scopes.What are a sitemap, the canonical tag and JSON-LD used for in technical SEO?
Open this question on its own pageThe interviewer wants to know whether you understand what problem each piece solves, rather than just knowing the names and copying a configuration.
A sitemap is an XML file that lists the addresses on your site, so that search engines can find pages without following every link. Google's guidance says they ignore
priorityandchangefreq, and uselastmodonly when the value always matches the page's real last change. Every value in the file must have XML special characters escaped.The canonical tag says which address is the main copy of a piece of content. When the same article can be reached at several addresses (with tracking parameters, with or without a trailing slash), canonical helps search engines group them into one. For a multilingual page, each version points its canonical at itself and lists the other versions with hreflang.
JSON-LD is a block of structured data (using the schema.org vocabulary) that says the page is an article, who the author is and when it was published. When you embed it in a script tag, replace the
<character with<, becauseJSON.stringifydoes not sanitize strings and a title containing</script>could break out of the tag.One practical note: none of these guarantees a top ranking, they only help search engines understand the page correctly. Preview environments need noindex, and only production should be indexed.
Bonus points: mention that a sitemap can also be wrong in ways that are hard to see, for example when it is served as a static file and so does not update when a new post goes out, and that you should have a way to check that on a live environment.
What is ISR in Next.js, and how is it different from a static page or rendering on every request?
Open this question on its own pageThe interviewer wants to know that you understand the three ways to build a page and can choose the right one for each kind of content, rather than only knowing the abbreviations.
There are three ways. A static page (SSG) is built once at build time and stays as it is, so it is very fast but only changes when you redeploy. Rendering on every request (SSR, or dynamic) builds a fresh page each time someone views it, so it is always up to date, but every view costs one round of processing and data queries. ISR (incremental static regeneration) sits in between: the page is stored like a static page, and Next.js rebuilds it in the background in one of two ways.
- By time. Set
export const revalidate = 60and after 60 seconds the next request still gets the old version but triggers a rebuild. When the new version is ready, later requests get it. This means readers never wait for data. - On demand. When content changes, the code calls
revalidatePathorrevalidateTagto mark the cache as expired, and the next visit rebuilds it.
How to choose: content that rarely changes and has many readers, like blog posts or product pages, suits ISR. Content specific to each user, like an account page, needs rendering on every request. Content that never changes, like an about page, suits a static page.
Two more points to add. One is the trade-off: ISR lets readers see an old version for a short time, so it doesn't suit data that must be accurate to the second, such as stock levels or balances. The other is that if you use a route to refresh on demand, you have to protect it with a secret, otherwise anyone can call it and force the server to rebuild again and again.
Bonus points: say that ISR needs a running Node.js server, so you can't use it when you export the blog as fully static HTML files.
- By time. Set
What is refactoring, and how do you make sure the code still behaves exactly the same afterwards?
Open this question on its own pageThe interviewer wants to know whether you can tell changing structure apart from changing behavior, and whether you have the habit of protecting yourself with tests. Refactoring means changing the internal structure of code so it is easier to read and change, without changing its outside behavior. Renaming a variable, splitting a long function into small ones and pulling repeated code into one place are all refactoring. Adding a feature or fixing a bug is not, because those change behavior.
A good answer follows this order:
- Have tests before you touch the code. If the code has none, write characterization tests: tests that capture what the code does today, even when that behavior is a bit odd. They don't ask "is it right?", only "is it different from before?"
- Work in small steps and run the tests after each one. When a test goes red you know which step caused it, because only one small thing changed.
- Don't mix refactoring with behavior changes. If you spot a bug halfway through, note it and fix it in a separate commit. Keeping the rename commit apart from the code-move commit also makes the review faster.
- Know what is an "inside name" and what is an "outside name". Renaming a local variable is safe. Renaming a field in the JSON an API returns breaks everything that reads that field, even though your code still compiles.
A concrete example makes the answer more convincing. Say you are cleaning up a small server that returns a list of orders. You rename the field
khachtocustomerto match the rest of the code, and your characterization test on the server's response goes red at once, because the admin page reads exactly thekhachfield. Without that test, the bug would only show up when a user sees an empty column.Bonus points: say when you would not refactor, for example when a deadline is close and that code is not what you are changing, or when you can't test it yet. Refactoring at the right time, in a small scope, fits a pull request that is easy to review better than cleaning the whole project in one go.
When do you log at warn and when at error, and what should you never put in a log?
Open this question on its own pageThe interviewer wants to know whether you see logs as a tool for the next person to find a cause, and whether you know how to classify events so that real errors don't get buried.
A simple classification that is easy to remember is to ask "whose fault is it?". The
infolevel is for normal things, for example an order was received. Thewarnlevel is for when the caller sent something wrong, such as a request rejected because the data is invalid (a 4xx status): the server did its job correctly. Theerrorlevel is for when the server did something wrong or hit a situation nobody foresaw (a 5xx status), and a person needs to look.The reason to separate them: if everything is logged as
error, a website that sends five hundred bad orders creates five hundred red lines that hide the one real server error. Filtering byerrorat two in the morning is only useful when theerrorlevel means something.A good log line answers four questions: what happened, when, in which request, and how important it is. That is why every request should get its own ID (a request id) attached to all of its lines. If you wrap one error in another, keep the original in
causeso the log can still tell the real reason.As for what not to log: personal data such as names and phone numbers, and secrets such as passwords, tokens and API keys. Log files get copied to many places, so anything that goes into a log should be considered leaked outside the project. When you reject a request because its data is wrong, log the field name and the reason, not the value the user sent.
Bonus points: mention that you shouldn't swallow errors with an empty
catchblock, and that when you return a 500 to the caller you only give a code to look up, while the internal details stay in the log.At the end of an interview, "Do you have any questions for us?" What should you ask?
Open this question on its own pageThere is no single right answer to this one, but there is a way to answer that wastes the chance: "No, thank you." Your question tells the interviewer what you care about, and their answer helps you decide whether you want to work there.
Prepare four or five questions in advance and pick two or three depending on the time left. Good questions are usually tied to a detail in the job ad or to something the interviewer just said:
- About the work: "In the first three months, what does a junior usually work on?", "How does the team review code, and how long does a pull request usually take to be merged?"
- About learning: "How often does a mentor meet a new hire?", "What does a junior who does well after the probation period look like, in your view?" The last one is especially useful, because the answer is exactly the standard you will be judged against.
- About the process: "After today, are there more rounds, and when could I expect feedback?"
What to avoid:
- Questions the job ad or the conversation has already clearly answered, because they show you weren't paying attention.
- Asking about salary or days off in a technical round with the team lead. Ask the recruiter, or in a later round.
- Generic questions copied from the internet when you don't really care about the answer.
Bonus points: ask a follow-up based on something the interviewer told you during the interview, for example "you mentioned earlier that the team is moving to Next.js, will juniors get to be part of that?" It shows that you listened and didn't just read out prepared lines.
If you could rebuild your personal project from scratch, what would you change?
Open this question on its own pageThis question checks whether you look back at your own work. "I think it's fine, I wouldn't change anything" is the weakest answer, because it suggests you learned nothing from the project.
A good answer usually has three parts:
- One specific decision you would change, with the reason. For example: "I'd write the table of examples and the tests before writing the function that reads amounts. The first time, I wrote the code first, and the message 'train ticket 32K' was read as 32 billion đồng."
- Something you would keep, to show you can tell good decisions from bad ones. For example: "I'd still reject a message that contains two amounts, because recording the wrong amount is worse than asking the user to send it again."
- A lesson that carries over to other work, such as cutting scope early, thinking about the server's time zone, or asking real users for feedback sooner.
Avoid two traps: criticising the technology you chose without being able to say why, and describing a change so big that it sounds like throwing everything away and starting over.
Bonus: prepare this answer in advance for the first project on your resume, because interviewers often ask it right after "tell me about your project".
Tell me about the project on your resume you're most proud of. Which part did you build yourself?
Open this question on its own pageThe interviewer asks this to check three things: whether you really built the project, how deeply you understand it, and whether you can explain a piece of technical work so someone else understands it.
A good answer takes about 2 minutes, in this order:
- The problem: what the project does, and for whom. One sentence is enough.
- Your role: for a team project, say clearly which part was yours, for example "I built all of the login and permissions". Don't say "we did" for everything.
- One technical decision: which approach you chose, why, and what alternatives you considered. For example: "I stored images in file storage and kept only their paths in the database, so we wouldn't go over the database size limit."
- One difficulty and how you got past it: a specific bug, and how you found its cause.
- If you did it again: one thing you would do differently. This shows you can look back at your own work.
Avoid:
- Reciting a feature list as if you were reading the README.
- Claiming a part you can't answer the next question about. Interviewers often dig two or three levels deeper into exactly the part you claim.
Prepare ahead: for every project on your resume, write down the 5 points above and practise saying them out loud at least once.
Tell me about yourself in about one minute.
Open this question on its own pageThis question often opens an interview, and the person asking doesn't need to hear your whole resume again. They want to see what you choose to talk about in one minute, and whether you can say it clearly. A good answer usually has four parts, one or two sentences each:
- Who you are and what you do: "I'm a recently graduated frontend developer, working mainly with React and TypeScript."
- One concrete piece of evidence: a project, with its hardest part and the result. For example: "I built an app that records spending from text messages. The hard part was reading amounts the way people in Vietnam write them, like 35k or 1tr2 (1.2 million). I solved it with a table of examples where every row is a test. The app has 21 tests and a few classmates use it every day."
- What you're learning or want to get better at, stated specifically: "I'm learning to write tests for React components."
- Why you're here: connect it to the role, for example the company uses React and TypeScript, or its product has a lot of mobile users.
Things to avoid:
- Reading out your resume in chronological order, starting from high school.
- Adjectives nobody can check, like "dynamic, eager to learn, works well under pressure".
- Mentioning a project you can't answer the next question about, because interviewers often dig straight into the project you just brought up.
Prepare beforehand: write it down, then practise saying it out loud a few times with a timer to keep it to about a minute. Keep it consistent with your LinkedIn headline and About section, because the interviewer may have just read them.
Why are you applying for this position at our company?
Open this question on its own pageThis question checks one simple thing: did you read the job ad and look into the company, or are you sending the same application everywhere? The interviewer doesn't need to hear praise for the company. They want to see you connect what they need with what you have done.
A good answer usually has three points, one or two sentences each:
- One specific detail from the ad or about the product. For example: "The ad says the frontend team writes tests for their own work and does code review. I'm looking for exactly that kind of place."
- Evidence that you fit that detail. "My personal project is an expense-tracking app written with React and TypeScript, with 21 unit tests. I'm used to working through pull requests from my team project at university."
- What you want to learn here, said honestly and specifically: "I've never worked with a large codebase that many people edit at once, and I want to learn how to do that from the team."
What to avoid:
- Generic sentences that fit any company: "a dynamic, professional environment", "a big company with lots of opportunities".
- Talking only about what you would receive (salary, a short commute) without saying what you would contribute. Those reasons are completely legitimate, but they shouldn't be your only point.
- Saying something wrong about the company's product. If you haven't been able to try it, say what you have read about it.
Prepare ahead: reread the job ad, underline the two requirements you have the clearest evidence for, and spend ten minutes on the company's website or product. Keep the CV you sent to this exact company, because the interviewer will ask questions based on it.
Bonus points: finish with a question back about the daily work, for example how the team reviews code at the moment.
How is authentication different from authorization? Where should you check permissions in a web application?
Open this question on its own pageAuthentication answers the question "who are you?", for example signing in with an email and password. Authorization answers "what are you allowed to do?", for example only the author may edit posts. The two are separate, and the most common mistake is to treat the first as if it included the second.
A concrete example: a blog writes the rule "anyone who is signed in may edit posts", because it assumes that only the author has an account. If the sign-in service also lets people sign up on their own, anyone can create an account, sign in validly and edit posts. That person was authenticated correctly, but never authorized.
As for where to check permissions, a good answer names three layers and what each one is for:
- The interface (hiding buttons, redirecting): only so users see what they can use. It protects nothing, because people call the API directly.
- The server (API routes, Server Actions, middleware): every point that receives a request must check again by itself, and not rely on which pages the user went through.
- The database (for example PostgreSQL Row Level Security): the last layer, still correct when a layer above forgot a check somewhere.
Two more points that show you have done this for real:
- The information used for authorization must live somewhere the user can't edit. With Supabase, that is
app_metadata, notuser_metadata. - On the server you must verify the token's signature and not just read the cookie, because users can set cookies in their own browser.
Bonus points: mention the codes 401 (not authenticated) and 403 (authenticated but not allowed), and the fact that a newly granted or revoked permission only takes effect when the token is refreshed.
What is a unit test? How do you know a test you wrote actually works?
Open this question on its own pageA unit test automatically checks a small piece of code (a function, a module): given this input, the result must be that. It runs fast and needs no server or database, so you can run it every time you save a file and before every merge.
The second half of the question is the part the interviewer really wants to hear. A test works when it turns red when the code is wrong. A few signs worth mentioning:
- The expected result comes from outside the code. Write
expect(orderTotal(order)).toBe(70000)because you know 2 coffees × 35,000 is 70,000. Don't recompute the expected value with the same formula you're testing, because then the test compares the code with itself and is always green. - Break the code on purpose. After fixing a bug, temporarily remove the fix and run the test again. If it's still green, it isn't checking the right thing.
- Include the cases that must be rejected, not only the ones that work. For example, a price parser should throw on
"35,000"instead of guessing. - The test name tells you what it protects, like "2 salt coffees at 35,000 cost 70,000", so whoever turns it red understands straight away without asking.
Bonus points: say that tests for each small function aren't enough. You also want a test that goes through the real data path (read raw data → normalize → total), because bugs often hide where a function is correct but no longer gets called.
- The expected result comes from outside the code. Write
Why do you have to validate input on the server even though TypeScript already has types?
Open this question on its own pageThe interviewer wants to know whether you can tell compile-time types apart from real data at runtime, and whether you think about the fact that data from outside can't be trusted.
TypeScript types only exist while you write and compile code. When the program runs, the types are erased, so
JSON.parse(body)returns whatever the sender put in, and writingas Orderis only you making a promise to the compiler, not a check. Data from a request, a file or another API is untrusted until it has been validated.What happens when you don't validate:
- Silent errors. A quantity that is text makes the multiplication come out as
NaN, which becomesnullwhen converted to JSON, and the total is wrong without any error. - Errors that spread. A record missing a field makes the processing function throw, and if that function runs over a whole list, one bad record breaks it for everyone.
- Security holes. The sender can also set fields they shouldn't be allowed to set, such as
idor permissions.
The right approach is to validate right where data enters the system, with a schema library such as Zod: declare the valid shape, call
safeParse, and if it fails return a 400 with the list of errors per field. After that step, the rest of the code uses the type inferred from the schema and trusts it. Declare incoming data asunknowninstead ofany, so the compiler forces you to check it before you use it.An important point to make: validate once at the door, not scattered everywhere, and tell errors caused by the sender (400) apart from errors of the server (500).
Bonus points: mention limiting the size of the body that can be sent, and deciding whether to allow unknown fields (rejecting them outright is usually safer than silently ignoring them).
- Silent errors. A quantity that is text makes the multiplication come out as
Why should you compress images before storing them, and should you do it in the browser or on the server?
Open this question on its own pageThe interviewer wants to know that you understand how images affect storage size and page load speed, and whether you think about the details that are easy to get wrong when processing images.
The main reason is that original photos from a phone or screenshots are often several MB to tens of MB, while the page shows them a few hundred pixels wide. Storing the originals fills your storage fast (free plans often give only a few hundred MB to a few GB) and makes readers on mobile networks download data they don't need. The biggest saving usually comes from scaling the image down to a sufficient size, for example 1600 px on the long side, which is still sharp on a retina screen. Changing the format to WebP or AVIF saves more, but how much depends on the image, so measure on real images.
For where to compress, there are two directions. Compressing in the browser (redrawing the image onto a canvas) saves bandwidth during upload and costs the server nothing, which suits the case where the uploader is a trusted author. Compressing on the server (for example with sharp) suits cases where you don't trust the uploader, or need several sizes for several screens, but it costs time and memory in the processing function. A framework image component (like
next/image) only optimizes at serving time, and the original stays in storage.Details that are easy to get wrong: portrait photos carry an EXIF Orientation tag, so you must rotate the pixels before stripping metadata, otherwise the image shows up sideways. A GIF may be animated, so don't recompress it like a still image. An already small image can come out heavier after compression, so keep the original when the compressed version is not smaller.
Bonus points: mention that stripping metadata also hides the GPS location in phone photos, and that a size limit on the storage side works as a second line of defense.
What is an embedding, and how does embedding search differ from keyword search?
Open this question on its own pageThe interviewer wants to know that you understand embeddings as a concrete tool with strengths and weaknesses, not as an impressive buzzword. They also want to see whether you know how to check that it is really better.
An embedding turns a piece of text into a vector, a list of numbers, using a model trained so that two passages with similar meaning end up with vectors that are close together. To search, you embed your documents ahead of time and store the vectors. When a question comes in, you embed it with the same model and take the passages whose vectors are closest, usually measured with cosine similarity.
The main differences from keyword search:
- Keywords compare words, embeddings compare meaning. A customer writes "compensate", the document says "refund": counting words sees different words that matter, while an embedding still ranks that passage high.
- Embedding scores only mean something relatively. Many models return scores packed into a narrow range, so you compare ranks rather than reading the score as a percentage.
- Keywords are stronger for codes, proper names and exact strings. If you search for a product code or a function name, an exact match is more trustworthy than a match on meaning.
An important point to make: don't assume embeddings are always better. The right approach is to prepare a set of sample questions, each with the passage that answers it, run both methods, and count how many questions each one finds enough information for. That set of questions also exposes wrong assumptions. For example, embedding the section title along with the text sounds helpful but can measure worse.
Bonus points: mention that embedding models have an input length limit (many cut off around 512 tokens), so documents need to be split into small enough passages, and that using the prefixes the model expects (like
query:andpassage:in the e5 family) also affects quality.How does an agent loop work, and how do you stop an agent from running forever?
Open this question on its own pageAn agent loop is the code wrapped around a model that lets it work in several steps instead of answering once. Each round does four things:
- Send the whole
messageshistory to the model, along with the list of tools. - Append the model's reply, word for word, to the history.
- If the model did not ask for a tool (with Claude,
stop_reasonis nottool_use), that is the final answer and the loop ends. - If it did, run every tool it asked for, collect the results into one message, add it to the history and go around again.
The important idea is that the model decides the number of steps, not the code. For example, a customer writes "find my order by phone number, and if it hasn't shipped yet, add one more iced milk coffee": the model calls, in turn, find order, check status and add item, and only then answers. The code just repeats.
So the second half of the question is the part the interviewer really wants to hear. Mention:
- A limit on model calls (say 6). When the limit is hit, stop, return a safe reply and write a log entry for someone to look at, instead of throwing an error or looping on.
- Rules live inside the tools, not only in the prompt. The add-item function itself refuses an order that is already being delivered, even if the model calls things in the wrong order.
- Tool errors go back to the model (
is_error) so it can change course, but you need to guard against the model retrying the same call forever. - Cost grows with the number of rounds: every round resends the whole history, so a 4-step question costs more than 4 times a 1-step question in tokens.
Bonus points: add that a time limit or a token budget should sit next to the round limit, and that SDKs often ship a tool runner that runs this loop for you.
- Send the whole
What is a context window? How does a chatbot "remember" a conversation, and what do you do when the conversation gets too long?
Open this question on its own pageThe context window is all the text the model can read in a single call: the system prompt, the tool definitions, every message you send along, and even the reply it is writing. Its size is measured in tokens, and each model has its own limit.
The first thing the interviewer wants to hear is that the model's API is stateless. It remembers nothing between two calls. A chatbot "remembers" because your code keeps the array of messages and sends all of it again on every turn. If you forget to send it, the customer's second message reaches the model like the first sentence of a stranger.
The consequence is that the longer the history, the more each call costs. The Nth call resends the N−1 earlier turns, so the total number of tokens in the whole conversation grows much faster than the number of messages. With models that have a large context window, you usually run into cost and latency before you hit the limit.
When a conversation gets long, there are three common approaches, and you can combine them:
- Sliding window: send only the last few turns. You must cut at a turn boundary, because a tool result (
tool_result) has to come right after the matching tool call (tool_use). Cutting by message count easily splits a pair in two and the API rejects it. - Structured memory: your code pulls out the important facts (order ID, customer name) from tool results and puts them in the system prompt. Less information, but never wrong.
- Summarizing: ask the model to summarize the old part into a short paragraph. It remembers more, but may leave out or misstate details, so don't use it for numbers that must be exact.
Bonus points: say that you would measure the real input tokens (the
usagefield in the response) before optimizing, keep the full history in a database and only trim the copy that is sent, and that you know the difference between memory within one conversation and long-term memory across sessions.- Sliding window: send only the last few turns. You must cut at a turn boundary, because a tool result (
What is MCP, and how is it different from tool calling?
Open this question on its own pageThe interviewer wants to know whether you can tell the mechanism by which a model asks for a tool to be run apart from the way tools are packaged and shared between applications.
Tool calling is a capability of the model: you describe the tools (name, description, input schema) in the request you send, and the model returns a block saying which tool it wants to call and with what input. Your own application is the one that runs the tool and sends the result back. The model doesn't run anything itself.
MCP (Model Context Protocol) is an open standard for AI applications to connect to outside tools and data. A tool is written once in an MCP server, and any application with an MCP client can list and call it. The two are not mutually exclusive: the application gets the list of tools from the MCP server, converts it to the format the model's API accepts (tool calling), and then when the model asks for a tool, forwards the call to the server. In short: tool calling is how the model asks for a tool to be run, and MCP is how tools are packaged so that many applications can use them.
When MCP is worth it: when one set of tools serves more than one application, or when you want to use someone else's existing server. If you have a single application and the tools rarely change, an ordinary function in the same code is simpler, because MCP adds a process and a protocol layer.
On safety, the MCP specification recommends that there should always be a human who can deny a tool call, and that hints such as
readOnlyHintshould be treated as untrustworthy unless the server is trusted. The server still has to check input and permissions itself, because the input is written by the model.Bonus points: mention that with stdio, stdout is reserved for the protocol so logs must go to stderr, and that you should tell protocol errors apart from errors while running a tool (returned as a result with
isError).What is prompt injection, how do direct and indirect attacks differ, and how do you defend against them?
Open this question on its own pageThe interviewer wants to know whether you realize that a model cannot tell your instructions apart from text someone else wrote, and whether you put your protection where it can be trusted rather than in a note inside the prompt.
Prompt injection happens when text controlled by someone else gets into the context and makes the model behave in ways you did not intend. A direct attack is when the user's own message does it, for example "ignore all previous instructions". An indirect attack is when the text sits inside data the agent reads, such as a notes field, a web page or a document, and the user may not even have written it. OWASP ranks this as the number one risk for applications built on language models, and notes that it is unclear whether there is any foolproof way to prevent it.
That is why the defenses belong in code: least privilege (the agent has no tools for sensitive actions such as refunds, it only hands the case to a person), permission checks inside each tool using an identity your application authenticated rather than one the model claims, putting only the fields it needs into the context, filtering the output, and having a human approve high-risk actions. Instructions in the system prompt and marking content as untrusted are still worth doing, but they are an extra layer only.
You should also say that an output filter based on regular expressions can always be bypassed by rephrasing, and it can block correct sentences by mistake, so you have to test it with both bad and good inputs.
Bonus points: mention turning the attacks you know about into tests you can rerun, and note that testing with a fake model only proves the code around the model, not the behavior of the real model.
What is an index in a database? Should you index every column?
Open this question on its own pageAn index is an extra data structure (usually a B-tree) that finds rows by a column's value without scanning the whole table, like the index at the back of a book.
You shouldn't index every column, because:
- Every
INSERT,UPDATEandDELETEalso has to update the indexes, so writes get slower. - Indexes take up storage.
- Columns with few distinct values (such as a
statuswith only 2 values) usually don't gain much.
create index posts_published_at_idx on posts (published_at desc) where status = 'published';A good answer mentions measuring first with
EXPLAIN ANALYZE, then adding an index for the specific slow query.- Every
What is Row Level Security (RLS) in PostgreSQL, and how is it different from GRANT?
Open this question on its own pagePostgreSQL has two layers of permission control on a table, and a good answer tells them apart.
GRANTdecides which operations a role may perform on the table:select,insert,update,delete. It doesn't distinguish rows: once you grantselect, the role can read every row.Row Level Security decides which rows that role may touch. After you run
alter table ... enable row level security, every operation has to satisfy a policy, an SQL condition checked on each row. For a blog, for example:create policy "Anyone can read published posts" on posts for select to anon using (status = 'published' and published_at <= now());A few points worth making to show that you have used it for real:
- Turning on RLS with no policy denies everything by default. A
selectdoesn't raise an error, it returns 0 rows, so the page is empty even though the table has data. This is a very common mistake. - Writes are blocked silently: an
updateordeleteon rows you can't see affects 0 rows, while aninsertthat violates a policy raises an error. - The table owner, superusers and roles with BYPASSRLS skip RLS. So you must test policies as the application's own role (for example
set role anon), and the admin key must never live in the browser. - Why you need RLS when you already filter in code: with platforms that let the browser call the database directly through an API (like Supabase), the key sits on the client side and anyone can call it. A filter in your code protects nothing; a policy in the database is what blocks.
Bonus points: say that you should use both layers and
grantonly the operations that are needed, so that one wrongly granted permission doesn't expose or destroy data.- Turning on RLS with no policy denies everything by default. A
A Node.js API responds slowly. How do you find the cause before you fix anything?
Open this question on its own pageThe interviewer wants to know whether you work from measurements instead of guesses, and whether you understand why code that is fast on small data can be slow on large data.
The first step is to know where it is slow at the request level. If the server has logs that record the processing time of each request, you filter out which route is slow. If it doesn't, measure with
performance.now()or add that log first.The second step is to test your hypothesis by measuring each suspected part separately (formatting data, converting to JSON, a database query): if one part takes a few milliseconds out of several seconds in total, don't optimize it.
The third step is to use a profiler. For example, the
--cpu-profflag of Node.js writes a.cpuprofilefile, so you can see which function spends the most time of its own.One common cause is quadratic complexity: using
findorincludeson an array inside a loop. With a few hundred items you won't notice, but with tens of thousands it gets clearly slow. The fix is usually to use aMapor aSetand look things up by key.After the fix, verify two things: whether the result is still correct (keep the old version to compare, or write a test) and how much faster it is (measure several times, take the median, at several data sizes to see the shape of the growth curve).
Bonus points: mention that not every piece of code is worth optimizing, that the more readable version is a reasonable decision at small scale, and that a measurement is only valid for the machine and the data you measured on.
When does useEffect run in React? What is the dependency array for?
Open this question on its own pageuseEffectruns after React has updated the screen. It's for keeping a component in sync with something outside React: calling an API, subscribing to events, setting timers.- No dependency array: it runs after every render.
- An empty array
[]: it runs once, after the first render. - With values such as
[id]: it runs again wheneveridchanges.
The function returned from the effect is the cleanup function. It runs before the next effect and when the component is removed, for example to clear a timer or remove an event listener.
How do you handle it when you disagree with feedback in a code review?
Open this question on its own pageThe interviewer wants to know whether you can separate your ego from your code.
- Ask questions to understand the reviewer's reasons properly before you reply.
- Answer with evidence: measurements, documentation, the team's conventions, not gut feeling.
- If you still disagree and the issue is small, go with the reviewer's way or note it to discuss later; don't block the PR.
- If the issue is big, move to a direct conversation instead of a long argument in the comments.
One real example, told briefly and with a concrete outcome, is more convincing than a list of principles.
What is XSS, and how do you render user-written Markdown without opening the door to it?
Open this question on its own pageThe interviewer wants to know whether you understand XSS as untrusted content being run by the browser as code, and whether you know the places it gets in from, rather than only having memorized the name.
XSS (cross-site scripting) happens when user data is put into a page in a way the browser reads as HTML or code instead of as text. The attacker can then run code in your page, with the permissions of the person viewing it: read cookies, call APIs on their behalf, change what the page shows.
With Markdown, the main risk comes from three places:
- Raw HTML. Markdown allows HTML, so libraries pass it through to the output verbatim, including tags like
<img onerror=...>. A<script>tag inserted throughinnerHTMLdoesn't run, but many other tricks still do. - Links and images with dangerous protocols.
[see](javascript:...)runs code when clicked. - A misconfigured filter. An allow-list that is too wide, or a hand-written regex filter, is very easy to bypass.
There are two approaches worth mentioning. The first is to convert Markdown to HTML and then filter it with a dedicated sanitize library using an allow-list (DOMPurify, sanitize-html), keeping only the tags and attributes you need. The second is not to produce an HTML string at all: parse the Markdown into typed blocks and build the page with a framework like React, where text is set as the content of an element and so is escaped automatically. With the second approach you still have to check the addresses of links and images yourself, accepting only
http,httpsand paths inside the site.An important point to make: don't use
dangerouslySetInnerHTML(orinnerHTML) for content that hasn't been filtered, and every path that puts content on a page, including the preview box in an admin page, has to go through the same safe processing.Bonus points: mention Content Security Policy as a second line of defense that limits where code may run, and note that a safety condition deserves a test, for example tests for
javascript:anddata:addresses.- Raw HTML. Markdown allows HTML, so libraries pass it through to the output verbatim, including tags like
How would you design rate limiting for a public API?
Open this question on its own pageStart with clarifying questions: limit by whom (IP, API key, user), at what level, hard block or slow down, and does the system run on one server or many.
Common algorithms:
- Fixed window: count per one-minute window; simple, but requests pile up at the window edges.
- Sliding window: smoother, uses more memory.
- Token bucket: allows short bursts; the most common choice for APIs.
With several servers, the counter has to live somewhere shared, such as Redis with atomic operations. Return
429 Too Many Requestswith aRetry-Afterheader, and log it so you can spot abuse.At senior level, also talk about the trade-offs: accuracy versus cost, what to do when Redis fails (let everything through or block everything), and different limits for each customer plan.
When you start a new project, what criteria do you use to choose the technology?
Open this question on its own pageStart from the problem and its constraints, not from whatever technology is trending.
- Who will run and maintain it six months from now, and what the team already knows.
- The real constraints: budget, deadline, SEO, offline requirements, number of users.
- The cost of switching if you choose wrong, and whether there's a way out.
- The ecosystem, the documentation, and how easy it is to hire people who know it.
Compare three options at most against exactly those criteria and write the decision down (a short ADR, architecture decision record) so you can check later whether it still holds.