JavaScript Most Important Topics For Interview

If you’ve ever sat in a JavaScript interview, you know it’s never just about writing a for loop or fixing a bug. Interviewers love to dig deeper — testing how well you understand the “why” behind the code. JavaScript is full of little quirks and powerful features, and knowing them can be the key to landing your next job.

In this post, I’m going to walk you through the topics that actually matter in interviews. These are the things you’ll see again and again — from basics like variables and functions to trickier concepts like closures, promises, and the event loop. The goal here isn’t just to memorize answers, but to really get a grip on how JavaScript works so you can explain it confidently when it counts.

JavaScript Most Important Topics For Interview

Var, Let, and Const

Featurevarletconst
ScopeFunction scope (works inside the whole function where declared)Block scope (works only inside { } where declared)Block scope (works only inside { } where declared)
Re-declarationAllowed in the same scopeNot allowed in the same scopeNot allowed in the same scope
Re-assignmentAllowedAllowedNot allowed (once value is set, can’t change for primitive values)
HoistingHoisted to the top (initialized with undefined)Hoisted but not initialized (accessing before declaration gives error)Hoisted but not initialized (accessing before declaration gives error)
Default UsageOld way, avoid usingModern way for changeable valuesModern way for fixed values
Examplevar x = 5; x = 10;let y = 5; y = 10;const z = 5; // z = 10; Error

Scope In JavaScript

In JavaScript, scope defines where variables and functions are accessible in your code. It is more like the boundary or visibility of your variables.

Global Scope – Variables declared outside any function or block live in the global scope and can be accessed from anywhere.

Function Scope – Variables declared inside a function are visible only inside that function.

Block Scope – With let and const, variables inside {} are limited to that block.
Note: var does not have block scope (it’s function-scoped).

Lexical Scope – Inner functions can access variables from their parent function (this is the basis of closures).

Module Scope – In ES6 modules, variables are private to the file unless explicitly exported.

JavaScript
// Global Scope
let globalVar = "I am Global";

function outerFunction() {
  // Function Scope
  let functionVar = "I am Function Scoped";

  if (true) {
    // Block Scope
    let blockVar = "I am Block Scoped";
    var notBlockScoped = "I ignore block scope (var)";
    console.log(blockVar); // Accessible inside this block
  }

  // console.log(blockVar); // Error: blockVar not defined
  console.log(notBlockScoped); // Accessible because var is function scoped

  // Lexical Scope (closure)
  function innerFunction() {
    console.log(globalVar);   // from global scope
    console.log(functionVar); // from outer function
    // console.log(blockVar); // Error: not accessible here
  }

  innerFunction();
}

outerFunction();
console.log(globalVar); // Accessible globally

// Module Scope (in ES6 module files)
let moduleVar = "I am Module Scoped";
// This won't be available outside unless exported
// export { moduleVar };

Hoisting

Hoisting in JavaScript is a behavior where variable and function declarations are moved to the top of their scope before the code is executed. This means you can use a variable or function before you actually write it in the code. However, only the declaration is hoisted, not the value. For example, variables declared with var are hoisted and initialized with undefined, so you can access them before the declaration (but they’ll be undefined). On the other hand, variables declared with let and const are also hoisted, but they are not initialized — they stay in a Temporal Dead Zone until the code actually reaches their declaration line. If you try to access them before that, you’ll get a ReferenceError. Functions declared using the function keyword are fully hoisted, so you can call them even before writing them in the code.

1. Hoisting with var

JavaScript
/*Explanation: var a is hoisted and initialized with undefined, so you can access it before its declaration without an error, but the value will be undefined.*/

console.log(a); // Output: undefined
var a = 10;
console.log(a); // Output: 10

2. Hoisting with let

JavaScript
/* Explanation: let b is hoisted but not initialized. This creates a Temporal Dead Zone (TDZ) between the start of the scope and the line where it’s declared. Accessing it before declaration gives a ReferenceError. */

console.log(b); // ReferenceError
let b = 20;
console.log(b); // Output: 20

3. Hoisting with const

JavaScript
/* Explanation: Same as let, const is hoisted but not initialized and is in a TDZ until its declaration. */

console.log(c); // ReferenceError
const c = 30;
console.log(c); // Output: 30

4. Hoisting with Functions

JavaScript
/* Explanation: yestFunction declarations are fully hoisted, meaning the entire function body is moved to the top, so you can call the function before writing it. */

greet(); // Output: Hello!

function greet() {
  console.log("Hello!");
}
Declaration TypeHoistedInitialized Before DeclarationAccess Before DeclarationOutput Before Declaration
varYesYes (set to undefined)Allowedundefined
letYesNo (Temporal Dead Zone)Not AllowedReferenceError
constYesNo (Temporal Dead Zone)Not AllowedReferenceError
functionYes (with full function body)YesAllowedRuns normally

Closures

A closure is when a function remembers and has access to variables from the place where it was created, even after that place has finished running.
In simple term:
A function carries a backpack with it. Inside that backpack are all the variables it had access to when it was born. Even if the parent function is done running, the child function still has that backpack.

Why Do Closures Exist?

Closures happen because of lexical scoping (Lexical scoping in JavaScript means a function can access variables from the place where it was written, not where it’s called) in JavaScript.
This means:

  1. A function can use variables from its own scope.
  2. It can also use variables from its parent function’s scope.
  3. It can also access variables from the global scope.

When a function keeps that access alive even after the parent has finished executing — boom, you have a closure!

JavaScript
function outer() {
  let count = 0;
  
  return function inner() { // closure
    count++;
    console.log(count);
  };
}

const counter = outer(); 
counter(); // 1
counter(); // 2

How it works:

  1. Lexical scoping lets inner() access count because it’s written inside outer().
  2. Closure keeps count alive in memory even after outer() is done, so each call to counter() updates the same variable.

Where Closures are Used in Real Life

Closures are not just for fancy interview questions. You use them in:

  • Data privacy → Keeping variables safe from outside changes.
  • Function factories → Creating multiple similar functions with different “backpack” data.
  • Event handlers → Remembering data when an event happens later.
  • Timers → When using setTimeout or setInterval.

Callback Functions

A callback function is a function that is passed as an argument to another function, and executed later. Instead of running immediately, the callback is “called back” at the right time.

Think of it as telling someone: “Do this task, and once you’re done, call me back.”

JavaScript
// Example 1: Basic Callback
function orderCoffee(customerName, callback) {
  console.log(customerName + " ordered a coffee.");
  callback(); // callback runs after main task
}

function serveCoffee() {
  console.log("Coffee served to customer.");
}

orderCoffee("Abhishek", serveCoffee);


// Example 2: Anonymous Callback
// Passing a function directly as a callback
orderCoffee("Riya", function() {
  console.log("Enjoy your coffee!");
});


// Example 3: Asynchronous Callback (setTimeout)
// setTimeout simulates coffee brewing time
console.log("Barista starts brewing...");

setTimeout(function() {
  console.log(" Coffee is ready after 3 seconds!");
}, 3000);

console.log("Meanwhile, cleaning the counter...");


// Example 4: Callback in Array Method
// forEach uses a callback function for each item
let menu = ["Espresso", "Latte", "Cappuccino"];

menu.forEach(function(item) {
  console.log("Available drink: " + item);
});


// Example 5: Callback Hell
// Making coffee step by step with nested callbacks
function grindBeans(next) {
  console.log("Grinding beans...");
  next();
}

function brewCoffee(next) {
  console.log("Brewing coffee...");
  next();
}

function addMilk(next) {
  console.log("Adding milk...");
  next();
}

// Nested callbacks → callback hell
grindBeans(function() {
  brewCoffee(function() {
    addMilk(function() {
      console.log("Coffee is ready! Served with love.");
    });
  });
});

Why Do We Need Callbacks?

Callbacks are essential for:

  • Handling asynchronous tasks (like fetching data from APIs).
  • Making sure code runs in the right order.
  • Writing event-driven programs (like button clicks or user input).

Without callbacks, JavaScript wouldn’t be able to handle asynchronous operations properly.

Callback Hell

While callbacks are powerful, they can become messy when you have too many nested callbacks, especially in asynchronous code. This problem is often called “callback hell”:

JavaScript
doTask1(function() {
  doTask2(function() {
    doTask3(function() {
      console.log("All tasks done!");
    });
  });
});

This pyramid of nested functions is hard to read and maintain. That’s why modern JavaScript introduced Promises and async/await to simplify asynchronous code.

A callback function is a simple yet powerful concept in JavaScript. It allows you to pass a function into another function and run it later, making it essential for asynchronous programming. While callbacks can sometimes lead to complicated code, they are the foundation of modern features like Promises and async/await.

Scroll to Top