JavaScript (ES5 → ES2025) & TypeScript Basics
== and ===?In short: === (strict equality) compares type and value without conversion.
==(loose equality) converts operands to a common type first, which produces surprises ("" == 0is true,null == undefinedis true,[] == falseis true).- Always use
===(except, by some style guides,x == nullto check null or undefined).
var, let and const?In short: var is function-scoped, hoisted and initialised as undefined, can be re-declared, and creates a property on the global object at top level.
letandconstare block-scoped and hoisted but in the Temporal Dead Zone until their declaration (accessing them earlier throws).- Neither can be re-declared in the same scope.
constcan't be reassigned, but the object it references can still be mutated.- Default to
const, useletwhen reassigning, avoidvar.
In short: During the creation phase of an execution context, declarations are registered before code runs.
varvariables are hoisted and initialised toundefined.- Function declarations are hoisted with their body (callable before the line they appear on).
let,constandclassare hoisted but uninitialised (TDZ → ReferenceError if used early).- Function expressions and arrow functions follow the rules of the variable they're assigned to.
In short: string, number, bigint, boolean, undefined, null and symbol.
- They are immutable and compared by value.
- Everything else (objects, arrays, functions, dates) is an object compared by reference.
typeof nullreturns "object" due to a historical bug.
null and undefined?In short: undefined means a variable has been declared but not assigned, a missing property, or a function with no return value — set by the engine.
nullis an intentional "no value" assigned by the programmer.typeof undefinedis "undefined",typeof nullis "object";null == undefinedis true butnull === undefinedis false.??and?.treat both as nullish.
In short: A concise function syntax (x => x * 2).
- Differences: no own
this(lexically inherited — great for callbacks), noargumentsobject, can't be used as constructors withnew, noprototype, and implicit return for single expressions. - Don't use them as object methods or prototype methods that need dynamic
this.
In short: A closure is a function that remembers the variables from the scope where it was created, even after that scope has finished executing.
- Example:
function counter() { let c = 0; return () => ++c; }— each returned function has its own privatec. - Practical uses: data privacy, function factories, memoisation caches, debounce/throttle timers, partial application, event handlers capturing loop values (with
let).
In short: JavaScript executes on a single thread with a call stack.
- Asynchronous operations (timers, network, events) are handled by the browser/Node APIs, which queue callbacks when done.
- When the call stack is empty, the event loop first runs all microtasks (promise reactions,
queueMicrotask), then (in browsers) lets rendering happen, then picks the next macrotask (setTimeout, I/O, UI events). - That's why a resolved promise's
.thenruns before asetTimeout(..., 0)callback.
In short: A Promise represents the eventual result of an asynchronous operation, in state pending, fulfilled or rejected; .then/.catch/.finally register reactions.
Promise.allwaits for all and rejects on the first failure.allSettledwaits for all and returns each outcome.racesettles with the first settled promise (success or failure).anyfulfils with the first success and rejects with AggregateError only if all fail.
this work in JavaScript?In short: It depends on the call site: method call → the object before the dot;
- plain function call →
undefinedin strict mode (global object otherwise); - constructor with
new→ the new instance; call/apply/bind→ explicitly set;- event listeners with regular functions → the element;
- arrow functions →
thisof the enclosing lexical scope. - Losing
thiswhen passing methods as callbacks is fixed withbindor arrow functions.
call, apply and bind?In short: All set this explicitly.
call(thisArg, a, b)invokes immediately with individual arguments.apply(thisArg, [a, b])invokes immediately with an array of arguments.bind(thisArg, a)doesn't invoke — it returns a new function withthis(and optionally leading arguments) permanently bound, useful for callbacks and event handlers.
In short: Objects inherit directly from other objects through the internal [[Prototype]] link.
- When a property isn't found on an object, the engine looks up its prototype chain.
- Constructor functions share methods via
Constructor.prototype; Object.create(proto)creates objects with a chosen prototype;- ES6
class/extendsis syntax over the same mechanism (Dog.prototype.__proto__ === Animal.prototype).
In short: Attaching a single event listener to a common ancestor instead of each child, using event bubbling and event.target (often with closest()) to identify which child was interacted with.
- Benefits: fewer listeners (memory/performance), works for elements added later dynamically, simpler cleanup.
- Note: some events don't bubble (
focus,blur— usefocusin/focusout).
Debounce delays execution until calls stop for a given time (search-as-you-type, auto-save, resize end). Throttle guarantees execution at most once per interval (scroll position, mousemove, rate-limited API calls).
const debounce = (fn, ms) => { let t; return (...a) => { clearTimeout(t); t = setTimeout(() => fn(...a), ms); }; };
const throttle = (fn, ms) => { let last = 0; return (...a) => { const now = Date.now(); if (now - last >= ms) { last = now; fn(...a); } }; };In short: A shallow copy duplicates top-level properties, but nested objects are still shared references ({...obj}, Object.assign, arr.slice(), [...arr]).
- A deep copy duplicates everything recursively:
structuredClone(obj)(modern, handles Dates/Maps/cycles, not functions/DOM nodes), orJSON.parse(JSON.stringify(obj))(loses Dates, undefined, functions, Infinity), or lodashcloneDeep.
map, forEach, filter and reduce?In short: forEach iterates for side effects and returns undefined.
mapreturns a new array of the same length with transformed items.filterreturns a new array with items that pass a test.reduceaccumulates the array into a single value (sum, object, grouped map).- None of them mutate the original array (unless your callback does).
forEachcan't be stopped early — usefor...ofwithbreak, orsome/find.
async and defer on script tags?In short: Both download the script without blocking HTML parsing.
asyncexecutes as soon as it's downloaded — order not guaranteed, may run before the DOM is complete (good for independent scripts like analytics).deferexecutes after parsing finishes, in document order, before DOMContentLoaded (good for app scripts).- Module scripts are deferred by default.
In short: Unremoved event listeners and timers (setInterval never cleared), references to detached DOM nodes, accidental globals, closures capturing large objects, ever-growing caches/maps, and subscriptions not unsubscribed in SPA components.
- Find them with Chrome DevTools: Performance monitor (JS heap growing), Memory tab heap snapshots compared over time, "Detached elements" panel, and allocation timelines.
- Fix with cleanup on unmount,
WeakMap/WeakReffor metadata, and bounded caches.
for (var i=0;i<3;i++){ setTimeout(()=>console.log(i),0) }In short: It prints 3 3 3.
varis function-scoped, so all three callbacks close over the samei, and by the time the timers run (after the loop completes),iis 3.- Fixes: use
let(a new binding per iteration →0 1 2), or an IIFE/bindto capture the current value.
interface and type in TypeScript?In short: Both can describe object shapes and can be extended (interfaces via extends, types via intersections &).
- Interfaces support declaration merging (re-opening to add members, used to augment library types) and give clearer error messages for object shapes.
- Type aliases can represent anything: unions, tuples, primitives, mapped and conditional types.
- Common guidance: interfaces for public object contracts, types for unions and type-level programming.
In short: Debounce the input (e.g. 300 ms) so the request fires only after typing pauses;
- ignore queries shorter than 2–3 characters;
- cancel the previous in-flight request with
AbortControllerso stale responses don't overwrite newer results (or track the latest request id); - cache recent results;
- show a loading state.
- On the server, add rate limiting and make the search endpoint efficient (indexes, paging).
In short: The main thread is blocked by long synchronous work.
- Move parsing/processing into a Web Worker (postMessage the data or a File/Blob, return results);
- stream the file with
ReadableStream/ process in chunks; - better, process it on the server and fetch paginated or aggregated results;
- virtualise large lists when rendering;
- show progress.
- Measure with the DevTools Performance panel (long tasks).
In short: Suspect a memory leak.
- Reproduce the flow repeatedly while taking heap snapshots in DevTools and comparing them (look for growing arrays, detached DOM nodes, listeners).
- Check for intervals not cleared, event listeners/subscriptions not removed when views are destroyed, global caches growing unbounded, and large closures.
- Fix cleanup paths (component unmount/destroy hooks), use
AbortControllerto remove listeners, bound caches, and verify the heap stabilises.
In short: Start all requests in parallel and use Promise.allSettled, then render the fulfilled results and show an error placeholder for rejected ones.
- Add timeouts with
AbortSignal.timeout(ms)so a slow API doesn't hang the page, and retry idempotent failures with backoff. - If any single failure must fail the whole page, use
Promise.allinstead.