Skip to main content

Search

Search problems, hooks, and pages

JS & TypeScript Quick Recall

Last-minute cheatsheet — scan in 5–10 minutes before your interview. Key concepts, gotchas, and code snippets.

Closures & Scope

Closure

  • A function that remembers the variables from where it was created , even after that outer function has returned.
  • You don't opt in , every function automatically captures its surrounding scope ("lexical environment").
  • What it buys you: private state, factory functions, memoization caches, partial application.
const make = x => y => x + y; // closes over x
const add5 = make(5);
add5(3); // 8

Warning

Watch out

The loop trap: a var loop variable is ONE shared variable, so every async callback sees its final value. Declare it with let (fresh per iteration) instead.

Scope types

  • Global scope , declared outside everything, visible everywhere.
  • Function scope , var and parameters belong to the whole function, ignoring blocks.
  • Block scope , let / const live only inside the nearest { }.
  • Lexical scope , what a function can see is fixed by WHERE it is written, not where it is called from.
Hoisting

var vs let/const vs function declarations

  • var , registered AND initialised to undefined at the top of its scope. Read it before its line and you get undefined, not an error.
  • let / const , registered but locked until their line (the Temporal Dead Zone). Touching them early throws a ReferenceError.
  • function declaration , hoisted whole, body included. Callable from lines above where it is written.
  • function expression (const fn = () => {}) , it's just a variable holding a function, so only the variable name hoists, not the function.
console.log(a); // undefined (var)
console.log(b); // ReferenceError (let TDZ)
foo();          // works — function declaration
var a = 1;
let b = 2;
function foo() {}

Warning

Watch out

You can tell a TDZ error apart: it says "Cannot access 'b' before initialization" , a truly missing variable says "b is not defined".
this Keyword

4 binding rules (in priority order)

  • 1. new binding , new Fn() → this is the freshly created object.
  • 2. Explicit binding , call/apply/bind → this is whatever object you passed in.
  • 3. Implicit binding , obj.method() → this is obj, the thing left of the dot.
  • 4. Default binding , a bare fn() → this is globalThis (or undefined in strict mode).

Arrow functions

  • Arrow functions have NO this of their own.
  • They simply use the this of the surrounding code, captured where they are DEFINED , and nothing can rebind it.
  • They can't be constructors , calling one with new throws.
  • That capture is exactly why they're perfect for callbacks inside methods (the setInterval example below).
class Timer {
  seconds = 0;
  start() {
    setInterval(() => this.seconds++, 1000); // arrow — inherits 'this'
  }
}

Warning

Watch out

Don't write object methods as arrows: obj.method = () => {} does NOT get obj as this , it inherits from outside the object (often the global scope).
Event Loop

Execution order

  • 1. All synchronous code runs first, to completion, on the call stack.
  • 2. When the stack empties, the ENTIRE microtask queue is drained.
  • 3. Then ONE macrotask runs , and the cycle repeats.
  • Microtasks (the urgent queue): Promise .then/.catch, code after await, queueMicrotask, MutationObserver.
  • Macrotasks (the regular queue): setTimeout, setInterval, I/O callbacks, UI events.
console.log('1');                          // sync
setTimeout(() => console.log('4'), 0);     // macrotask
Promise.resolve().then(() => console.log('3')); // microtask
console.log('2');                          // sync
// Output: 1, 2, 3, 4

Warning

Watch out

A promise callback ALWAYS beats setTimeout(fn, 0) queued at the same time , microtasks jump the queue.
Promises & async/await

Promise states

  • pending → fulfilled (success) or rejected (failure) , and once settled, frozen forever.
  • .then(onFulfilled, onRejected) returns a NEW promise , that's what makes chains possible.
  • .catch(fn) is just shorthand for .then(undefined, fn) , it catches failures from anywhere earlier in the chain.
  • .finally(fn) runs either way and receives nothing , the place for cleanup like hiding a spinner.

Promise combinators

  • Promise.all([...]) , everything must succeed; the FIRST failure rejects the whole batch.
  • Promise.allSettled([...]) , waits for all, never rejects; gives a {status, value|reason} report per promise.
  • Promise.race([...]) , the FIRST to settle wins, success or failure (great for adding timeouts).
  • Promise.any([...]) , the first SUCCESS wins; only fails if all fail (AggregateError).
// Parallel fetches (don't await sequentially!)
const [user, posts] = await Promise.all([fetchUser(id), fetchPosts(id)]);

async/await gotchas

  • An async function ALWAYS returns a promise , even a plain return value comes wrapped.
  • await pauses only its own function , everything outside keeps running.
  • Independent tasks awaited one after another run one after another , start them together with Promise.all.
  • A rejected await throws , wrap in try/catch, or attach .catch() to the returned promise.
  • Never await inside forEach , it fires all the callbacks without waiting. Use for...of, or Promise.all(arr.map(async ...)).

Warning

Watch out

await inside forEach is silently broken: forEach ignores the promises your callback returns, so nothing actually waits.
ES6+ Essentials

Destructuring

  • Objects: const { a, b: alias, c = fallback } = obj , pick, rename, and default in one line.
  • Arrays: const [first, , third, ...rest] = arr , pick by position, skip with a gap, collect the tail.
  • Nested data in one go: const { user: { address: { city } } } = data.
  • Straight into function parameters: function fn({ id, name = "anon" }) {}.

Spread & Rest

  • Spread arrays: [...arr1, ...arr2] , copy or merge in one expression.
  • Spread objects: { ...obj1, ...obj2 } , merged; when keys collide, the later one wins.
  • Rest params: function fn(a, ...rest) , the extras arrive as a genuine Array.
  • Both copy ONE level deep only , nested objects inside are still shared with the original.

Warning

Watch out

Spread is a shallow clone: the copy has the same nested objects as the original. Mutate one, you've mutated both.

?. and ??

  • obj?.prop , undefined instead of a crash when obj is null/undefined.
  • arr?.[0] , the same safety for index access.
  • fn?.() , call it only if it exists.
  • a ?? b , fall back ONLY when a is truly missing (null/undefined) , 0 and "" survive.
  • a || b , falls back on ANY falsy value, replacing legitimate 0, "", and false too.

Warning

Watch out

0 ?? "default" gives 0, but 0 || "default" gives "default" , use ?? whenever 0 or "" are values you want to keep.
Array Methods

Transforming (returns new array)

  • map(fn) , transform every element; same length out as in.
  • filter(fn) , keep only the elements where fn returns true.
  • flatMap(fn) , map, then flatten one level , perfect when each item can produce zero, one, or many results.
  • flat(depth) , lift nested arrays up into the parent, one level by default.
[1,2,3].map(x => x * 2);           // [2,4,6]
[1,2,3,4].filter(x => x % 2 === 0); // [2,4]
[[1,2],[3,4]].flat();                // [1,2,3,4]

Aggregating (returns single value)

  • reduce(fn, init) , fold the array into anything: a sum, an object, another array.
  • find(fn) , the first element that passes the test, or undefined.
  • findIndex(fn) , its position instead, or -1.
  • some(fn) , "does at least one pass?" , stops at the first yes.
  • every(fn) , "do they all pass?" , stops at the first no.
  • includes(val) , is this value in the array? (and unlike indexOf, it can find NaN).

Sorting & mutating (mutates original!)

  • sort((a,b) => a - b) , sorts IN PLACE, and without that comparator it sorts numbers as text ([10, 2, 1] → [1, 10, 2]).
  • reverse() , also flips the array in place.
  • splice(i, n, ...items) , surgically remove/insert in place.
  • Want the original untouched? Copy first , [...arr].sort(comparator) , or use the ES2023 copies: toSorted / toReversed / toSpliced.

Warning

Watch out

sort() changes the original array , a classic React state bug. Spread first: [...arr].sort().
Prototype & Classes

Prototype chain

  • Every object holds a hidden link ([[Prototype]]) to another object, forming a chain that ends at null.
  • Property lookup walks that chain: own properties → prototype → its prototype → ... → null.
  • Object.create(proto) builds an object with exactly the prototype you choose.
  • Read the link with Object.getPrototypeOf(obj) , the __proto__ accessor works but is the legacy spelling.

ES6 Classes

  • Cleaner syntax over the same prototype system , not a new object model.
  • constructor() runs on new ClassName() , per-instance setup goes here.
  • In a subclass constructor, super() must run before you touch this.
  • static members live on the class itself, not on instances.
  • #field is genuinely private , inaccessible outside the class body, not just a naming convention.
  • Methods in the class body are stored once on the prototype and shared by every instance.
class Animal {
  #name;
  constructor(n) { this.#name = n; }
  speak() { return this.#name; }
}
class Dog extends Animal {
  speak() { return super.speak() + ': woof'; }
}
Common Gotchas

Type coercion traps

  • typeof null → "object" , a bug from 1995, kept forever. Check with value === null.
  • typeof [] → "object" too , arrays need Array.isArray().
  • NaN !== NaN , the one value not equal to itself. Check with Number.isNaN().
  • The party tricks: [] + [] → "" · [] + {} → "[object Object]" · {} + [] → 0 (the {} parses as an empty block!).
  • Always give parseInt its radix , parseInt("08", 10) , so the base is never guessed.

Warning

Watch out

Default to === everywhere. Loose == coercion rules are genuinely hard to memorise and breed subtle bugs.

Pass by value vs reference

  • Primitives (number, string, boolean, null, undefined, symbol, bigint) are copied , the function gets its own value.
  • Objects and arrays pass a copy of the REFERENCE , both variables point at the same underlying object.
  • So mutating an object parameter inside a function mutates the caller's object too.
  • Defend by copying: spread/Object.assign for one level, structuredClone for a full deep clone.
TypeScript Essentials

type vs interface

  • interface , open: extendable with extends, and re-declarable to add members (declaration merging).
  • type , closed (no merging), but it can name ANYTHING: unions, intersections, primitives, tuples.
  • Reach for interface when describing object shapes and public APIs.
  • Reach for type when you need unions, intersections, or computed/mapped types.

Generics

  • <T> declares a type placeholder that gets filled in where the function/type is used.
  • Constrain what's allowed: <T extends SomeType> , "any T, as long as it fits SomeType".
  • Give it a default: <T = string>.
  • Combine them: <T, K extends keyof T> , "K must be a real key of T" (the typed-lookup pattern below).
function pick<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key];
}

Key Utility Types

  • Partial<T> , every property becomes optional (perfect for update payloads).
  • Required<T> , the reverse: everything mandatory.
  • Pick<T, K> , a slimmed-down type keeping only the K keys.
  • Omit<T, K> , the complement: everything EXCEPT the K keys.
  • Record<K, V> , an object type with keys K and values V (a typed dictionary).
  • ReturnType<typeof fn> , extract what a function returns, no manual retyping.
  • NonNullable<T> , T with null and undefined stripped out.

Type Guards

  • typeof x === "string" , narrows x to string inside the branch.
  • x instanceof Date , narrows to Date.
  • "prop" in obj , narrows to the union member that has that property.
  • Custom guards: function isUser(x): x is User { ... } , your own check, trusted by the compiler.
  • Discriminated unions: switch on a literal "kind"/"type" field and each case narrows automatically.

satisfies

  • expr satisfies Type checks expr against Type WITHOUT widening it , unlike a `:` annotation (which widens) or `as` (which skips checking entirely).
  • You get both: a compile error if a key is wrong, and the narrowest inferred type afterwards for every value.
  • The classic use case: a config/palette object where each individual value should stay as specific as possible after validation.
const palette = { red: [255, 0, 0], green: '#0f0' } satisfies Record<string, string | number[]>;
palette.green.toUpperCase(); // still known as string

Template Literal Types

  • Build new string literal types the same way you build template strings at runtime , interpolating a literal type into a backtick-delimited type produces a new, more specific string literal type.
  • Interpolating a union produces the cross-product of every combination , great for deriving event-name types from an object's own keys.
  • Ships with Uppercase<T>, Lowercase<T>, Capitalize<T>, Uncapitalize<T> built in.

using / await using

  • A const-like declaration that auto-runs cleanup ([Symbol.dispose]()) the moment it leaves scope , on a normal exit, an early return, or a thrown error, no try/finally needed.
  • await using is the async version, for AsyncDisposable ([Symbol.asyncDispose]()).
  • Resources dispose in last-in-first-out order, like a stack.

in / out Variance

  • out T (covariant): T only in output/return positions , a Getter<Dog> can stand in for a Getter<Animal>.
  • in T (contravariant): T only in input/parameter positions , the substitution direction flips.
  • The compiler verifies the annotation is actually true , it's not just documentation.

import type & Awaited<T>

  • import type { Foo } from "./mod" guarantees the import is fully erased at compile time , zero runtime trace, needed by single-file transpilers like esbuild/SWC.
  • Awaited<Promise<Promise<number>>> resolves to number , it recursively unwraps nested Promises, mirroring what repeated await calls actually do at runtime.
  • NoInfer<T> (TS 5.4+) blocks a parameter from influencing generic inference , useful for a "default value" argument that shouldn't widen the inferred type.