Back to News & Insights
JavaScript September 10, 2026 · 10 min read

JavaScript and TypeScript Interview Questions Explained With Real Production Examples

Most explanations of these concepts stop at the definition. Scope gets a rule about hoisting....

JavaScript and TypeScript Interview Questions Explained With Real Production Examples

Most explanations of these concepts stop at the definition. Scope gets a rule about hoisting. Closures get a counter function that increments a number nobody would ever ship. The event loop gets a diagram. any versus unknown gets a one-line rule. Generics get a toy example with numbers and strings that never shows up in a real codebase.

That's enough to answer a question in an interview. It's not enough to actually recognize these patterns when they show up in your own code, which is the thing that matters once you're past the interview and actually shipping something.

This is the first in a short series working through concepts that come up constantly in interviews, explained the way I'd actually explain them to someone on my team, with real examples from production code, not just the textbook version.

var, let, and const are the three ways to declare a variable in JavaScript. var is function-scoped and can be redeclared and reassigned freely. let is block-scoped and can be reassigned but not redeclared in the same scope. const is block-scoped and can't be reassigned after its initial value is set, though an object or array it points to can still be mutated internally.

The rule everyone learns is "use const by default, let when a value changes, avoid var." The part that's harder to see from that rule alone is what it looks like once real state is involved, not just a single reassignment.

Take pagination on a leads or submissions list, the kind of thing Formgrid.dev's dashboard actually does:

pageSize never changes for the life of that list, so const says so directly. currentPage changes every time someone clicks through the pagination, so let is the honest choice. The value of picking correctly here isn't stylistic; it's that anyone reading this code later can tell, without tracing every line, which values are safe to assume are fixed and which ones move. var doesn't offer that signal at all, and it doesn't respect block scope the way let and const do, which is reason enough to leave it out of new code entirely.

A closure is a function that keeps access to variables from the scope it was created in, even after that outer scope has already finished running.

createApiClient() finishes running the moment it returns, but the object it hands back still remembers baseUrl. That's the closure: get and post keep access to a variable from a scope that's technically already finished executing.

The reason this matters beyond the definition is what it lets you do: create multiple independent clients from the same function, each remembering its own configuration.

Each one is a separate closure over a different baseUrl, with zero shared state between them. This is the same shape I'd reach for anywhere a function needs to remember configuration without a class: event handlers, callbacks, factories, or anything that needs private state without exposing it directly.

Promise.all() takes an array of promises and resolves once every one of them has resolved, returning their results in the same order. If any single promise in the array rejects, Promise.all() rejects immediately, discarding the results of everything else.

Formgrid's dashboard loads several independent things on page load: lead pipeline stats, recent submissions, integration status, and account usage. None of them depend on each other's results.

If each of those takes something like 500ms, 800ms, 600ms, and 300ms, waiting for them one at a time adds up to roughly 2.2 seconds before the dashboard can render anything.

Run concurrently instead; the total time is close to the slowest single request, around 800ms, not the sum of all four. Promise.all is the right tool specifically when the requests are genuinely independent, and you need all of them to proceed.

Promise.allSettled() also takes an array of promises, but it waits for every one of them to finish regardless of outcome, and returns an array describing each result as either fulfilled, with its value, or rejected, with its reason. Nothing gets discarded because one promise failed.

The catch with Promise.all is that a single rejection fails the entire batch. That's fine when every result is required before anything can render. It's the wrong choice when one piece failing shouldn't block the others.

Say the AI Smart Inbox category breakdown widget on that same dashboard has a bad moment and its request fails, while the lead stats, submissions, and integrations requests all succeed fine. With Promise.all, that one failure would throw away three perfectly good results along with it.

Want to discuss this further?

Book a free strategy call with our team to see how these insights apply to your specific business goals.

Book a consultation