← ALL GUIDES

INTERVIEW WALKTHROUGH · NODE.JS

Node.js Refactoring Interview: SOLID Principles on Real Code

This is a real interview task. A senior full stack position, backend heavy, at a well-known European fintech. The company sent over a codebase days before the call with one instruction: explore the code, tell us what is good or bad. Bogdan took the interview, spent 45 minutes refactoring one controller, and got an offer above the stated budget.

No AI assistants. No running the code. Just reading, explaining, and refactoring under pressure. Every step below is a decision from the actual interview, and every decision traces back to a named best practice: REST, SOLID, DRY, or a JavaScript fundamental.

THE CONCEPTS

The mental models, one by one

  1. 01

    Reading a Codebase in a Framework You Never Used

    The assignment was written in NestJS. Bogdan had never used it. That is normal — companies test you on code you did not write, often in flavors you have not touched.

    The way through is architecture, not framework trivia. NestJS implements MVC: controllers, services, modules, DTOs, entities. If you go for a senior backend position, you are expected to know what a controller and a service are and transfer that knowledge to whatever flavor they hand you.

    First move: take a top-level view of the whole repo before touching anything. Most of the code was standard. The code smells clustered in one controller — that is where the 45 minutes went.

    NestJSMVCcode reading
  2. 02

    What a Code Smell Actually Is

    A code smell is a bad practice, or something that can be improved, measured against a known best practice. It is not an opinion. It is a deviation from a principle you can name.

    Example: modifying global state is a code smell in a JavaScript framework built on immutability, because it goes against the framework's own principle. Every smell you call out in an interview should come with the principle it violates.

    code smellsbest practices
  3. 03

    Fix the Endpoint: REST Says POST Returns the Created Object

    The controller had a POST method on the transactions path that returned a promise resolving to a string. That is not RESTful. In REST, a POST returns the created entity — here, a transaction object.

    First change: fix the return type to the transaction entity. You do not need to know what the framework decorator does under the hood. You need to know what a RESTful endpoint looks like and spot when code is not one.

    RESTAPI designHTTP
  4. 04

    Functions Are Actions — Name Them Like Actions

    The method was called something like commission. Functions are actions, and their names should say what they do. Commission is not a good name for something that creates a transaction.

    Renamed it to createTransaction. Together with the corrected return type, the endpoint now satisfies the REST contract. Two small changes, both explained out loud, both tied to a principle.

    namingclean code
  5. 05

    Single Responsibility: Kill the Stringify, Extract the Formatting

    The handler called JSON.stringify on the response object. Serialization is the framework's job — it happens when the response goes over the network. Doing it in the controller is a stolen responsibility. Delete it, return the object.

    Next: a repeated parseFloat-toFixed formatting expression. Repeated formatting logic is the easiest extraction there is — pull it into a small formatAmount arrow function and call it in both places.

    That is the single responsibility principle from SOLID applied twice in two minutes. Neither change is clever. Both are visible, explainable, and correct.

    SOLIDSRPrefactoring
  6. 06

    A Get Function That Writes to the Database Is a Side Effect

    Two functions, getAmountWithExchange and getAmountWithoutExchange, computed a commission amount — and then inserted a transaction into the database. A get that mutates the database is misleading. That is the side effect to call out.

    Apply single responsibility: one function computes the commission amount, a separate function creates the transaction. Use them independently. Modifying state inside something named get will burn whoever reads the code next.

    side effectsSRPNode.js
  7. 07

    DRY: Two Overlapping Functions Become Two Shared Methods

    Both functions calculated the commission the same way and both persisted a transaction. The overlap was almost total — the only real difference was one call to an external exchange rate service.

    Do the easiest thing first. Extract getCommissionAmount as one async method. Extract persistTransaction as another. Call both from each original function. Repeated code gone, and the actual difference between the two paths is now visible.

    That is DRY combined with single responsibility. Also: the nested ternary deciding which path to run got flattened into an if statement. In an interview, the brute force readable version beats the clever one.

    DRYSOLIDrefactoring
  8. 08

    Never Use any Without Saying Why

    Under time pressure a parameter got typed as any. That is allowed — if you say it out loud. The line was: we leave any here for now, but I would never use any in a production codebase.

    If they ask why, you answer: any breaks type safety, which defeats the point of a static typing system. Offer a generic type as the real fix. In TypeScript interviews they watch for exactly this — silent any is a red flag, explained any is a pragmatic call.

    TypeScripttype safetygenerics
  9. 09

    The Node.js Test: Wrap a Subscription in a Promise

    The hardest part. The exchange rate call used a subscribe method with a callback, and the callback's return value vanished into a vacuum — the outer function never returned the transaction. Event-based code does not compose with normal control flow.

    The fix is the Promise constructor: return a new Promise, resolve it with the new transaction when the external service finishes, reject it on error. Now the caller can await it like any other async function.

    This is where they test real Node.js knowledge. Practice creating promises by hand until it is fast. Do not interview for a senior position in the JavaScript ecosystem without being able to convert callback code to async/await under pressure.

    Promiseasync/awaitNode.jscallbacks
  10. 10

    Leaked Abstractions: Exchange Rate Logic Belongs to the Exchange Service

    One more smell: an if statement checking the currency — return an exchange rate of one for Euro, call the service otherwise — sat in the transaction controller. That is exchange rate domain logic scattered outside its domain.

    Move the check into the getExchangeRate method, or better, into the exchange service itself: hand it a currency, let it decide. The exchange service had leaked its abstractions into the transaction service. Naming that pattern out loud is exactly the kind of thing that got the strong review here.

    abstractiondomain logicservices
  11. 11

    How to Prepare for a Refactoring Interview

    Four things. Know REST in practice — not what a REST API is, but whether a given endpoint is restful and how to fix it. Know SOLID applied to real code, without getting purist. Know JavaScript fundamentals cold: async/await, promises, the Promise constructor. And train speed.

    The pressure of an interview makes you nervous and makes you forget. Solidified best practices are what you fall back on. Every decision you make on their code should come from a best practice you can name — do that, and you stand out.

    One more thing: the code did not need to compile at every step. Communicating intent matters more than a green build you cannot get anyway. Flag type errors, say you will fix them, keep moving.

    interview prepSOLIDRESTfundamentals
A preview of the free Senior training

Free Senior Training.

One session, the whole system: the fundamentals, the interview strategy, and the plan to the offer. Watch it free.