Debug School

rakesh kumar
rakesh kumar

Posted on

JavaScript Concepts Every Developer Should Know: What, Why, When, Where, and How

JavaScript powers interactive websites, APIs, dashboards, and mobile apps. Understanding its core concepts helps you write predictable code, avoid bugs, and build responsive applications.

This guide explains the 12 concepts shown in the reference image: scope, closures, hoisting, the event loop, promises, async/await, this, prototypes, destructuring, array methods, debounce and throttle, and memory management. Each section includes a definition, syntax, a practical example, and the reason to use it.

The examples use modern JavaScript. Browser examples such as document, fetch, and requestAnimationFrame require a browser or an environment that provides the equivalent APIs.

Quick overview

# Concept Simple meaning Common use
1 Scope Where a variable can be accessed Keep variables inside a function or block
2 Closure A function remembers its surrounding variables Private counters and callbacks
3 Hoisting and TDZ Declarations have different behavior before initialization Avoid confusing variable access errors
4 Event loop Coordinates queued work with the call stack UI events, timers and asynchronous operations
5 Promise Represents an eventual asynchronous result API calls and file operations
6 Async/await Syntax for working with promises Readable API requests
7 this A value determined by how a function is called Object methods and classes
8 Prototypes Objects can inherit properties through a chain Shared methods
9 Destructuring Extract values into variables Read API responses and arrays
10 map, filter, reduce Transform, select, and combine array elements Vehicle listings and totals
11 Debounce and throttle Control how often a function runs Search inputs and scroll events
12 Memory management Manage reachable objects and resources Prevent memory leaks

1. Scope

What: Scope defines where a variable is accessible in code.

Why: Restricting visibility makes code easier to understand and reduces unintended changes.

When and where: Use block scope for temporary values, function scope for values inside a function, and module scope for shared code within one module.

How — syntax:

const globalValue = 'Available outside the function';

function showExample() {
  const functionValue = 'Available inside this function';

  if (true) {
    let blockValue = 'Available inside this block';
    console.log(globalValue, functionValue, blockValue);
  }

  // console.log(blockValue); // ReferenceError: outside its block
}

showExample();
Enter fullscreen mode Exit fullscreen mode
Declaration Scope Can be reassigned?
let Block Yes
const Block No; object contents can still be modified
var Function, or global depending on context Yes

Practical example:

function calculateRental(days) {
  const dailyPrice = 500;
  if (days >= 7) {
    const discount = 300;
    return days * dailyPrice - discount;
  }
  return days * dailyPrice;
}
console.log(calculateRental(7)); // 3200
Enter fullscreen mode Exit fullscreen mode

The discount variable exists only inside the if block. Prefer const by default and use let when reassignment is required. var is function-scoped, not block-scoped.

2. Closures

What: A closure is a function bundled with access to variables from its surrounding lexical environment.

Why: It lets a function retain state without exposing that state as a global variable.

When and where: Use closures for private state, event handlers, factories, and configurable functions.

How — syntax and example:

function createBookingCounter() {
  let count = 0;

  return function () {
    count += 1;
    return count;
  };
}

const counter = createBookingCounter();
console.log(counter()); // 1
console.log(counter()); // 2

const anotherCounter = createBookingCounter();
console.log(anotherCounter()); // 1: separate state
Enter fullscreen mode Exit fullscreen mode

What happens: The returned function can still access count after createBookingCounter() returns. A closure retains access to a binding; it does not necessarily freeze a snapshot of the value.

Security perspective: Closures can hide variables from ordinary external access, but they do not encrypt data or create a security boundary against compromised code running in the same application.

3. Hoisting and the temporal dead zone

What: JavaScript processes declarations as part of setting up an execution context. Different declarations behave differently before their initialization.

Why: Understanding this helps avoid undefined values and ReferenceError exceptions.

When and where: This matters whenever code references a variable or function before its declaration is reached.

How — syntax:

console.log(price); // undefined
var price = 500;

// console.log(city); // ReferenceError: temporal dead zone
let city = 'Bangalore';

// console.log(rate); // ReferenceError: temporal dead zone
const rate = 5;

showMessage(); // Works: function declaration is available
function showMessage() {
  console.log('Welcome');
}
Enter fullscreen mode Exit fullscreen mode
Declaration Before initialization Explanation
var undefined Binding is initialized to undefined
let ReferenceError Binding is in the temporal dead zone (TDZ)
const ReferenceError Binding is in the TDZ until initialized
Function declaration Callable before its textual position in the same scope Function binding is initialized during setup

Practical rule: Declare variables before using them. A function expression assigned to let or const follows that variable's TDZ rules.

const calculateTotal = (days, price) => days * price;
console.log(calculateTotal(2, 500)); // 1000
Enter fullscreen mode Exit fullscreen mode

4. Event loop

What: The event loop coordinates execution of queued work. In a browser, JavaScript runs synchronous code on the call stack, while the environment manages events, timers, and other asynchronous work.

Why: It helps explain why a timer or promise callback does not run immediately, and why heavy synchronous work can freeze the UI.

When and where: This applies to browsers and JavaScript runtimes. Node.js also has an event loop, but its scheduling details differ from browsers.

How — browser example:

console.log('1: start');

setTimeout(() => console.log('4: timer task'), 0);
Promise.resolve().then(() => console.log('3: microtask'));

console.log('2: end');
Enter fullscreen mode Exit fullscreen mode

Expected output:

1: start
2: end
3: microtask
4: timer task
Enter fullscreen mode Exit fullscreen mode

The synchronous code finishes first. The promise callback is a microtask; the timer callback runs as a later task. A delay of zero does not mean immediate execution. Browsers normally drain the microtask queue at the relevant checkpoint before moving to the next task. Excessive microtasks can delay other work and rendering.

Practical use: If processing a very large list blocks a page, split the work or use a Web Worker for suitable CPU-heavy work. Making a function async by itself does not move computation to another thread.

5. Promises

What: A promise represents a result that may become available later.

Why: It provides a consistent way to handle asynchronous success and failure.

When and where: Use promises with APIs such as fetch, asynchronous libraries, and functions that must return a future result.

State Meaning
Pending The operation has not settled
Fulfilled It settled successfully with a value
Rejected It settled with a reason or error

A promise settles once: it becomes fulfilled or rejected. Fulfilled and rejected are alternative outcomes, not sequential stages.

How — syntax:

const booking = new Promise((resolve, reject) => {
  const vehicleAvailable = true;
  if (vehicleAvailable) {
    resolve('Booking confirmed');
  } else {
    reject(new Error('Vehicle unavailable'));
  }
});

booking
  .then(message => console.log(message))
  .catch(error => console.error(error.message))
  .finally(() => console.log('Booking attempt finished'));
Enter fullscreen mode Exit fullscreen mode

Practical API example:

fetch('/api/vehicles')
  .then(response => {
    if (!response.ok) throw new Error(`HTTP ${response.status}`);
    return response.json();
  })
  .then(vehicles => console.log(vehicles))
  .catch(error => console.error('Unable to load vehicles:', error));
Enter fullscreen mode Exit fullscreen mode

fetch() can fulfill for an HTTP 404 or 500; check response.ok. Network failures can reject the promise.

6. Async / await

What: async and await provide syntax for working with promises in a more sequential style.

Why: They make multi-step asynchronous code easier to read and handle errors with try/catch.

When and where: Use them in functions that call asynchronous APIs, such as booking or profile requests.

How — syntax:

async function loadVehicles() {
  try {
    const response = await fetch('/api/vehicles');
    if (!response.ok) throw new Error(`HTTP ${response.status}`);

    const vehicles = await response.json();
    console.log(vehicles);
    return vehicles;
  } catch (error) {
    console.error('Could not load vehicles:', error);
    throw error; // Let the caller decide what to do.
  }
}

loadVehicles().catch(() => {
  // Update the UI with a suitable error message.
});
Enter fullscreen mode Exit fullscreen mode

Key points: An async function returns a promise. await pauses that function until the awaited value settles; it does not block the entire JavaScript thread while waiting.

Independent requests:

const [vehiclesResponse, citiesResponse] = await Promise.all([
  fetch('/api/vehicles'),
  fetch('/api/cities'),
]);
if (!vehiclesResponse.ok || !citiesResponse.ok) {
  throw new Error('Could not load required data');
}
const [vehicles, cities] = await Promise.all([
  vehiclesResponse.json(),
  citiesResponse.json(),
]);
Enter fullscreen mode Exit fullscreen mode

Place this inside an async function or a module supporting top-level await. Promise.all rejects if any input rejects; it does not automatically cancel the other requests.

7. The this keyword

What: In an ordinary function, this is generally determined by how the function is called. Arrow functions inherit this from their surrounding context.

Why: Correct this handling prevents confusing bugs in object methods, callbacks, and classes.

When and where: Use it to refer to the object involved in an object method or a class instance.

How — syntax:

'use strict';

const vehicle = {
  brand: 'Honda',
  showBrand() {
    console.log(this.brand);
  },
};

vehicle.showBrand(); // Honda

const detached = vehicle.showBrand;
// detached(); // TypeError in strict mode: this is undefined

const bound = vehicle.showBrand.bind(vehicle);
bound(); // Honda
Enter fullscreen mode Exit fullscreen mode
Call style Typical this
vehicle.showBrand() vehicle
fn() in strict mode undefined
fn.call(obj) / fn.apply(obj) The provided object, subject to function semantics
new Person() The newly created instance for a normal constructor
Arrow function Inherited from the surrounding context

Practical class example:

class Vehicle {
  constructor(name) { this.name = name; }
  showLater() {
    setTimeout(() => console.log(this.name), 100);
  }
}
new Vehicle('Scooter').showLater(); // Scooter
Enter fullscreen mode Exit fullscreen mode

Arrow functions are useful here because they capture the method's this. They do not have their own this and are usually unsuitable as object methods that need dynamic this.

8. Prototypes

What: JavaScript objects can inherit properties and methods from another object through a prototype chain.

Why: Many objects can share methods without storing a separate copy on each instance.

When and where: Use classes or prototype-based patterns for related objects and reusable behavior.

How — syntax:

class Vehicle {
  constructor(name) {
    this.name = name;
  }
  describe() {
    return `Vehicle: ${this.name}`;
  }
}

const bike = new Vehicle('Bike');
const car = new Vehicle('Car');
console.log(bike.describe()); // Vehicle: Bike
console.log(bike.describe === car.describe); // true
console.log(Object.getPrototypeOf(bike) === Vehicle.prototype); // true
Enter fullscreen mode Exit fullscreen mode

The method is found through the prototype chain. Class syntax is built on JavaScript's prototype system.

Security perspective: Do not blindly merge untrusted keys into objects. Guard against unsafe keys such as __proto__, and use safe parsing, validation, and current libraries to reduce prototype-pollution risks.

9. Destructuring

What: Destructuring extracts values from arrays or object properties into variables.

Why: It reduces repetitive property access and makes code easier to read.

When and where: Useful with API responses, function arguments, configuration objects, and arrays.

How — syntax:

const user = { name: 'Ashwani', city: 'Bokaro' };
const { name, city, role = 'customer' } = user;
console.log(name, city, role); // Ashwani Bokaro customer

const prices = [500, 800];
const [bikePrice, carPrice] = prices;
console.log(bikePrice, carPrice); // 500 800
Enter fullscreen mode Exit fullscreen mode

Practical function example:

function showVehicle({ brand, price, city = 'Unknown' }) {
  return `${brand}: ₹${price}/day in ${city}`;
}
console.log(showVehicle({ brand: 'Honda', price: 500, city: 'Goa' }));
Enter fullscreen mode Exit fullscreen mode

Defaults apply when a value is undefined, not when it is null. Destructuring is not validation: validate untrusted data before relying on it.

10. map, filter, and reduce

What: These array methods transform elements, select elements, and combine values.

Why: They express common data operations clearly.

When and where: Use them for UI lists, reports, filtering available vehicles, and calculating totals.

Method Syntax Purpose
map items.map(item => result) Create a new array of transformed values
filter items.filter(item => condition) Create a new array of matching elements
reduce items.reduce((total, item) => ..., initial) Combine elements into one result

How — practical example:

const vehicles = [
  { name: 'Bike', price: 500, available: true },
  { name: 'Car', price: 1500, available: false },
  { name: 'Scooter', price: 400, available: true },
];

const names = vehicles.map(vehicle => vehicle.name);
const available = vehicles.filter(vehicle => vehicle.available);
const dailyTotal = available.reduce((sum, vehicle) => sum + vehicle.price, 0);

console.log(names);      // ['Bike', 'Car', 'Scooter']
console.log(available);  // Bike and Scooter objects
console.log(dailyTotal); // 900
Enter fullscreen mode Exit fullscreen mode

map and filter create new arrays but do not deep-copy object elements. Give reduce an appropriate initial value, especially for possibly empty arrays.

11. Debounce and throttle

What: Debounce waits until calls stop for a defined period. Throttle limits how often work executes during repeated calls.

Why: Both reduce unnecessary work during frequent user events.

When and where: Debounce search input; throttle continuous events such as scroll or resize, where periodic updates are useful.

How — debounce syntax:

function debounce(fn, delay) {
  let timer;
  return function (...args) {
    clearTimeout(timer);
    timer = setTimeout(() => fn.apply(this, args), delay);
  };
}

const searchInput = document.querySelector('#city-search');
const searchCities = debounce(event => {
  console.log('Search for:', event.target.value);
}, 300);

searchInput.addEventListener('input', searchCities);
Enter fullscreen mode Exit fullscreen mode

This simple trailing debounce runs after 300 ms without another call. The page must contain #city-search. If searching an API, also handle out-of-order responses or cancel stale requests.

How — simple leading throttle:

function throttle(fn, interval) {
  let lastRun = -Infinity;
  return function (...args) {
    const now = performance.now();
    if (now - lastRun >= interval) {
      lastRun = now;
      fn.apply(this, args);
    }
  };
}

const onScroll = throttle(() => {
  console.log('Scroll position:', window.scrollY);
}, 200);
window.addEventListener('scroll', onScroll);
Enter fullscreen mode Exit fullscreen mode

This implementation runs on the leading call and drops calls during the interval; it does not guarantee a trailing call. Different implementations may offer leading and trailing options.

Situation Choice Reason
Search while typing Debounce Wait for a pause
Track scroll position Throttle Update periodically
Animate visual movement Often requestAnimationFrame Coordinate visual updates with the browser
Protect a server from abuse Server-side rate limiting Client-side timing can be bypassed

12. Memory management

What: JavaScript automatically manages much of its memory. Garbage collection can reclaim objects that are no longer reachable by the running program.

Why: Objects retained longer than needed can increase memory usage and slow or crash an application.

When and where: Watch memory in long-lived dashboards, single-page apps, event listeners, timers, caches, and large data processing.

How — release references and resources:

let selectedVehicle = { id: 101, image: 'bike.jpg' };
console.log(selectedVehicle.id);
selectedVehicle = null; // Removes this reference; collection is not immediate.
Enter fullscreen mode Exit fullscreen mode

Event listener cleanup:

const button = document.querySelector('#refresh');

function refreshVehicles() {
  console.log('Refresh vehicles');
}

button.addEventListener('click', refreshVehicles);

// Later, when this feature is removed:
button.removeEventListener('click', refreshVehicles);
Enter fullscreen mode Exit fullscreen mode

Timer cleanup:

const timerId = setInterval(() => console.log('Checking updates'), 5000);
// When updates are no longer needed:
clearInterval(timerId);
Enter fullscreen mode Exit fullscreen mode
Common issue Why memory remains used Practical action
Unbounded cache Application keeps references forever Set a size or expiration limit
Forgotten timer Callback and referenced data stay reachable Clear the timer
Unneeded event listener Live target retains its callback Remove listeners when appropriate
Closure captures a large object The closure remains reachable Avoid retaining unnecessary state
Detached DOM nodes JavaScript still references removed elements Remove unnecessary references

Modern garbage collectors handle cycles, so circular references alone do not necessarily cause leaks. Unreachable objects are eligible for collection; the engine decides when to reclaim them. Use browser DevTools memory snapshots to investigate growth.

Common mistakes and safer choices

Concept Common mistake Better practice
Scope Use a global variable for every value Restrict scope with const, let, functions, and modules
Closure Assume captured data is encrypted Treat closure state as normal application memory
Hoisting Use variables before declaration Declare before use
Event loop Run large synchronous loops on the UI thread Batch work or use a worker when suitable
Promises Assume HTTP error responses reject fetch Check response.ok
Async/await Forget rejection handling Use try/catch or handle the returned promise
this Pass an unbound method as callback Bind it or use an appropriate wrapper
Prototypes Merge untrusted object keys without controls Validate keys and use secure libraries
Destructuring Assume extraction validates input Validate types and required fields
Array methods Assume new arrays deep-copy objects Copy objects explicitly when needed
Debounce/throttle Treat client-side timing as server protection Enforce rate limits on the server
Memory Expect immediate garbage collection Release references and clean up resources

References

Big colorful JavaScript architecture diagram

Follow the paths from a user action through synchronous code and asynchronous operations. Scope, closures, hoisting, this, and prototypes affect how code behaves. Array methods, destructuring, and rate controls help organize work. Memory management applies across the whole application.

flowchart TD
    USER["👤 User action: search or book"]:::blue --> EVENT["Browser event or request"]:::blue
    EVENT --> STACK["Call stack: synchronous JavaScript"]:::yellow

    STACK --> SCOPE["1. Scope: where variables are visible"]:::purple
    STACK --> HOIST["3. Hoisting and TDZ: declaration timing"]:::purple
    STACK --> THIS["7. this: call context"]:::purple
    SCOPE --> CLOSURE["2. Closures: retain outer bindings"]:::purple
    THIS --> PROTO["8. Prototypes: shared behavior"]:::purple

    STACK --> DATA["Application data"]:::green
    DATA --> DESTRUCT["9. Destructuring: extract values"]:::green
    DATA --> ARRAYS["10. Map / filter / reduce"]:::green
    EVENT --> RATE["11. Debounce / throttle"]:::orange
    RATE --> ASYNC["Start asynchronous operation"]:::orange
    STACK --> ASYNC

    ASYNC --> PROMISE["5. Promise: pending"]:::cyan
    PROMISE -->|success| OK["Fulfilled"]:::green
    PROMISE -->|failure| ERR["Rejected"]:::red
    OK --> MICRO["Promise callbacks: microtask queue"]:::cyan
    ERR --> MICRO
    AWAIT["6. async / await: promise syntax"]:::cyan --> PROMISE
    TIMER["Timer or event task"]:::orange --> LOOP["4. Event loop"]:::yellow
    MICRO --> LOOP
    LOOP --> STACK

    STORAGE["12. Memory: reachable objects and resources"]:::pink
    CLOSURE -. "may retain references" .-> STORAGE
    DATA -. "may retain references" .-> STORAGE
    STORAGE --> CLEAN["Cleanup: references, listeners, timers, caches"]:::pink

    classDef blue fill:#dbeafe,stroke:#2563eb,color:#172554,stroke-width:2px;
    classDef yellow fill:#fef9c3,stroke:#ca8a04,color:#713f12,stroke-width:2px;
    classDef purple fill:#f3e8ff,stroke:#9333ea,color:#581c87,stroke-width:2px;
    classDef green fill:#dcfce7,stroke:#16a34a,color:#14532d,stroke-width:2px;
    classDef orange fill:#ffedd5,stroke:#ea580c,color:#7c2d12,stroke-width:2px;
    classDef cyan fill:#cffafe,stroke:#0891b2,color:#164e63,stroke-width:2px;
    classDef red fill:#fee2e2,stroke:#dc2626,color:#7f1d1d,stroke-width:2px;
    classDef pink fill:#fce7f3,stroke:#db2777,color:#831843,stroke-width:2px;
flowchart LR
    A["🔵 User and events"] --> B["🟡 Execution and event loop"]
    B --> C["🟣 Language behavior"]
    B --> D["🟢 Data operations"]
    B --> E["🔷 Promises and async"]
    C --> F["🩷 Memory and cleanup"]
    D --> F
    E --> F

    classDef blue fill:#dbeafe,stroke:#2563eb,color:#172554,stroke-width:2px;
    classDef yellow fill:#fef9c3,stroke:#ca8a04,color:#713f12,stroke-width:2px;
    classDef purple fill:#f3e8ff,stroke:#9333ea,color:#581c87,stroke-width:2px;
    classDef green fill:#dcfce7,stroke:#16a34a,color:#14532d,stroke-width:2px;
    classDef cyan fill:#cffafe,stroke:#0891b2,color:#164e63,stroke-width:2px;
    classDef pink fill:#fce7f3,stroke:#db2777,color:#831843,stroke-width:2px;
    class A blue;
    class B yellow;
    class C purple;
    class D green;
    class E cyan;
    class F pink;

Mermaid support depends on the publishing platform. If the blog does not render Mermaid, render the diagram to an image before publishing.

Diagram

Top comments (0)