JavaScript · Core JS

Core JS Interview Questions
With Answers & Code Examples

17 carefully curated Core JS interview questions with working code examples and real interview gotchas.

Practice Interactively →← All Categories
17 questions6 beginner5 core6 advanced
Q1Beginner

What is the difference between var, let, and const?

💡 Hint: Think about scope, hoisting, and reassignment

var is function-scoped and hoisted (initialized as undefined). let and const are block-scoped and in a Temporal Dead Zone before declaration.

var x = 1;
let y = 2;    // block-scoped, reassignable
const z = 3;  // block-scoped, binding locked

if (true) {
  var a = 'leaks out';
  let b = 'stays in';
}
console.log(a); // 'leaks out'
console.log(b); // ReferenceError

const doesn't make values immutable — objects/arrays can still be mutated. The binding is locked, not the value.

💡 Default to const, use let only when reassignment is needed. Avoid var.
Practice this question →
Q2Beginner

Explain closures with a practical example.

💡 Hint: A function that remembers its outer scope after the outer function returns

A closure is a function that retains access to its lexical scope even after the outer function has returned.

function makeCounter() {
  let count = 0;
  return {
    increment: () => ++count,
    decrement: () => --count,
    value:     () => count
  };
}
const counter = makeCounter();
counter.increment(); // 1
counter.increment(); // 2

Real-world uses: data encapsulation, factory functions, event handlers with state, memoization, partial application.

💡 Classic gotcha: var in a for-loop closure — all callbacks share the same variable. Fix with let or IIFE.
Practice this question →
Q3Beginner

What is hoisting in JavaScript?

💡 Hint: Declarations are moved to top of scope before execution

Hoisting moves declarations to the top of their scope during compilation — before execution. Initializations are NOT hoisted.

console.log(a); // undefined (not ReferenceError)
var a = 5;

greet(); // Works! Function declarations fully hoisted
function greet() { console.log('hello'); }

sayHi(); // TypeError
var sayHi = () => console.log('hi');

let/const are hoisted but live in a Temporal Dead Zone — accessing them before declaration throws a ReferenceError.

Practice this question →
Q4Core

Explain the event loop, call stack, and microtask queue.

💡 Hint: Synchronous → Microtasks (all) → Next Macrotask

JS is single-threaded. The call stack runs sync code. Async callbacks go into task queues. The event loop picks tasks when the stack is empty.

Microtasks (Promises, queueMicrotask) drain completely after each task before the next macrotask runs.

console.log('1');
setTimeout(() => console.log('2'), 0); // macrotask
Promise.resolve().then(() => console.log('3')); // microtask
console.log('4');
// Output: 1 → 4 → 3 → 2
💡 Order: Sync → All Microtasks → Next Macrotask → All Microtasks → ...
Practice this question →
Q5Beginner

What is the difference between == and ===?

💡 Hint: Type coercion vs strict type + value check

== performs type coercion. === checks value AND type — no coercion.

0 == ''          // true  (both coerce to falsy)
0 == '0'         // true
0 === '0'        // false
null == undefined  // true (special case)
null === undefined // false
NaN == NaN       // false (NaN never equals itself)

Always use ===. Use Number.isNaN() to check for NaN.

Practice this question →
Q6Beginner

What are the different types of scope in JavaScript?

💡 Hint: Global, function (local), and block scope — each has different variable rules

JavaScript has three types of scope:

  • Global scope — Variables declared outside any function or block. Accessible everywhere. In browsers, becomes a property of window.
  • Function scope — Variables declared with var inside a function are only accessible inside that function.
  • Block scope — Variables declared with let or const inside {} are scoped to that block.
var globalVar = 'everywhere';

function fn() {
  var funcVar = 'function only';
  if (true) {
    let blockVar = 'block only';
    var leaky = 'leaks to fn scope'; // var ignores blocks!
  }
  console.log(leaky);    // ✓ 'leaks to fn scope'
  console.log(blockVar); // ✗ ReferenceError
}

console.log(funcVar); // ✗ ReferenceError

The scope chain: When a variable isn't found in the current scope, JS looks up to the outer scope — all the way to global. Inner scopes access outer variables; outer scopes cannot access inner.

💡 Prefer const/let over var — they're block-scoped and avoid the leaky behavior of var.
Practice this question →
Q7Core

What is lexical scope and how does it affect closures?

💡 Hint: Scope is determined where code is written, not where it is called

Lexical scope (static scope) means a function's scope is determined by where it is written in the code — at author time — not by where it is called at runtime.

const x = 'global';

function outer() {
  const x = 'outer-fn';
  function inner() {
    console.log(x); // 'outer-fn' — closes over WHERE inner is DEFINED
  }
  return inner;
}

const fn = outer();
fn(); // logs 'outer-fn', NOT 'global'
// Even though fn() is called at global level, it remembers outer-fn's scope

This is what makes closures possible — a function carries its scope from where it was created, not from where it's invoked.

💡 Contrast with dynamic scope (Bash, Perl): if JS were dynamic, fn() would print 'global' because it's called there. Lexical scope is more predictable and is the reason closures work.
Practice this question →
Q8Advanced

What is an Execution Context and what are its two phases?

💡 Hint: Creation phase (hoisting, this binding) then execution phase — pushed onto the call stack

An Execution Context (EC) is the environment in which JavaScript code evaluates and executes. Every function call creates a new EC pushed onto the call stack.

Phase 1 — Creation Phase:

  • Scans for var declarations → hoisted and set to undefined
  • Scans for let/const → hoisted but placed in Temporal Dead Zone
  • Scans for function declarations → fully hoisted (name + body)
  • Determines the value of this
  • Sets up the scope chain (reference to outer environment)

Phase 2 — Execution Phase: runs code line by line, assigns actual values.

function example() {
  // Creation phase saw: var a → undefined, fn fully hoisted
  console.log(a);   // undefined (var hoisted)
  console.log(fn()); // 'works!' (function declaration hoisted)
  var a = 1;
  function fn() { return 'works!'; }
  console.log(a);   // 1 (now assigned)
}

Types of EC: Global EC (one per program), Function EC (one per call), Eval EC (avoid).

💡 The Global EC creates the global object (window/globalThis) and binds this = global object in its creation phase.
Practice this question →
Q9Core

What is the Temporal Dead Zone (TDZ)?

💡 Hint: let/const are hoisted but not initialized — accessing them before declaration throws

The Temporal Dead Zone is the period between when a let/const variable is hoisted (block start) and when it is initialized (the declaration line). Accessing it in this window throws a ReferenceError.

{
  // ← TDZ for 'a' starts here (block start)
  console.log(a); // ❌ ReferenceError: Cannot access 'a' before initialization
  let a = 5;      // ← TDZ ends, a is initialized to 5
  console.log(a); // 5
}

// typeof does NOT protect you in TDZ
console.log(typeof undeclared); // "undefined" — safe (never declared)
console.log(typeof a);          // ReferenceError if 'a' is in TDZ!

Why TDZ exists: Intentional design to catch bugs where you accidentally use a variable before its meaningful initialization. var silently returns undefined, masking errors.

💡 TDZ applies to let, const, and class declarations. Function declarations are fully hoisted with no TDZ.
Practice this question →
Q10Core

What does "use strict" do and why should you use it?

💡 Hint: Opt-in to stricter parsing — catches silent errors, changes some behaviors

Strict mode activates a restricted variant of JavaScript that converts silent errors into thrown errors and disables some confusing/dangerous features.

What it prevents:

  • Implicit globals — x = 5 throws ReferenceError (not window.x)
  • Duplicate parameter names — function fn(a, a) {} throws
  • Writing to read-only properties — throws instead of silently failing
  • this in standalone functions is undefined (not window)
  • delete on variables/functions — throws
  • Octal literals (0777) — throws
'use strict';

x = 5;             // ReferenceError — no more accidental globals
function fn(a, a) {} // SyntaxError

function test() {
  console.log(this); // undefined (not window)
}
test();

Good news: ES6 modules and classes are always in strict mode automatically. In modern code you rarely need to write 'use strict' explicitly.

💡 Enable strict mode per file or per function with the string 'use strict' at the top. Helps catch bugs early and enables engine optimizations.
Practice this question →
Q11Advanced

How does garbage collection work in JavaScript?

💡 Hint: Mark-and-sweep — unreachable objects are collected; common leak sources

JavaScript uses automatic garbage collection — memory is freed when objects become unreachable from the "roots" (globals + active call stack).

Mark-and-Sweep algorithm:

  1. Start from roots (global scope + call stack)
  2. Mark every reachable object
  3. Sweep — free everything NOT marked
let user = { name: 'Alice' }; // reachable via 'user'
user = null;                  // reference dropped → unreachable → GC'd

// Circular reference — NOT a problem for modern mark-and-sweep
let a = {}; let b = {};
a.ref = b; b.ref = a; // circular — but if a and b lose all external refs, both are GC'd

Common memory leak sources:

  • Forgotten setInterval holding references to DOM elements
  • Detached DOM nodes still referenced in JS variables
  • Event listeners never removed (removeEventListener)
  • Closures unintentionally capturing large objects
  • Unbounded caches / global arrays that grow forever
💡 Use WeakMap/WeakRef to associate data with objects without preventing GC. DevTools Memory tab → Heap Snapshot to hunt leaks.
Practice this question →
Q12Beginner

What is the difference between primitive and reference types?

💡 Hint: Primitives copy by value; objects copy by reference (the memory address)

Primitive types (string, number, boolean, null, undefined, symbol, bigint) are stored and copied by value.

Reference types (objects, arrays, functions) are stored as pointers. Variables hold a reference — assigning copies the reference, not the object.

// Primitives — copy by value
let a = 5;
let b = a;
b = 10;
console.log(a); // 5 — completely independent

// Objects — copy by reference
let obj1 = { x: 1 };
let obj2 = obj1;    // both point to THE SAME object in memory
obj2.x = 99;
console.log(obj1.x); // 99 — obj1 was mutated!

// Reference equality
console.log([] === []);   // false — different objects
console.log({} === {});   // false — different objects

// Functions receive references
function mutate(arr) { arr.push(1); }
const myArr = [];
mutate(myArr);
console.log(myArr); // [1] — original was mutated
💡 "Pass by value" in JS — always. But for objects, the VALUE being passed is the reference (memory address). So you can mutate the object, but you can't make the caller's variable point to a new object.
Practice this question →
Q13Advanced

What is Automatic Semicolon Insertion (ASI) and what are its gotchas?

💡 Hint: JS inserts ; in specific places — return on its own line is the classic trap

JavaScript automatically inserts semicolons in certain places during parsing to handle missing semicolons.

ASI rules (simplified): JS inserts ; when the next token would make the code invalid, and at the end of file.

// Classic trap: return on its own line
function getObj() {
  return    // ← ASI inserts ; HERE
  {
    data: 1  // unreachable!
  }
}
getObj(); // undefined! NOT the object

// Fix: opening brace on SAME line as return
function getObj() {
  return {
    data: 1
  };
}

// Trap 2: lines starting with (, [, /, +, -
const a = 1
const b = 2
[a, b].forEach(x => console.log(x)) // PARSED AS: 2[a,b].forEach(...)
// TypeError: Cannot read properties of undefined

// Fix: add semicolon to previous line, OR start with ;
const b = 2;
;[a, b].forEach(x => console.log(x)) // safe defensive semicolon
💡 Safest rule: always put return values on the same line as return. If not using semicolons, use defensive semicolons before lines starting with [, (, or /.
Practice this question →
Q14Core

What is BigInt and when do you need it?

💡 Hint: Arbitrary-precision integers — for values beyond Number.MAX_SAFE_INTEGER (2^53 - 1)

JavaScript's Number type (64-bit float) can only safely represent integers up to 2^53 - 1 = 9,007,199,254,740,991. BigInt handles arbitrarily large integers.

// Problem: precision loss with large integers
9007199254740993 === 9007199254740992; // true! — lost a bit

// BigInt — suffix with n
9007199254740993n === 9007199254740992n; // false ✓

const big = 99999999999999999999999999n;
const sum = big + 1n; // works perfectly

// Can't mix BigInt and Number directly
1n + 1; // TypeError: Cannot mix BigInt and other types
Number(1n) + 1; // 2 — explicit conversion
BigInt(5) + 3n; // 8n — explicit conversion

// Comparison with Number (ok with ==, not ===)
1n == 1;  // true (loose equality)
1n === 1; // false (strict, different types)

// No decimal support
10n / 3n; // 3n — truncates toward zero

// Math methods don't support BigInt
Math.max(1n, 2n); // TypeError
💡 Use BigInt for: large database IDs (64-bit integers from other languages), financial amounts where precision matters, cryptography, and any integer computation beyond 2^53.
Practice this question →
Q15Advanced

What is the rendering phase of the browser event loop?

💡 Hint: Between macrotasks: style → layout → paint → compositing — sync code blocks it

The browser event loop interleaves JS execution with rendering. Understanding this explains why sync code freezes the UI.

// Browser event loop order:
// 1. Pick one macrotask from the queue (e.g., setTimeout callback)
// 2. Execute it to completion
// 3. Drain ALL microtasks (Promises, queueMicrotask)
// 4. ← RENDER PHASE (if needed):
//      a. requestAnimationFrame callbacks
//      b. Style recalculation
//      c. Layout (reflow)
//      d. Paint
//      e. Composite
// 5. Repeat

// Why sync code freezes UI:
button.onclick = () => {
  // Render phase is BLOCKED until this finishes
  for (let i = 0; i < 1_000_000_000; i++) {} // 1 second of CPU
  updateDOM(); // user sees nothing during the loop
};

// requestAnimationFrame runs IN the render phase — perfect for animation
function animate() {
  element.style.left = (x++) + 'px'; // guaranteed to paint every frame
  requestAnimationFrame(animate);    // schedule for NEXT render phase
}
requestAnimationFrame(animate);

// setTimeout(0) yields to render, rAF aligns WITH render
button.onclick = () => {
  status.textContent = 'Loading...';
  setTimeout(() => heavyWork(), 0); // allows repaint of 'Loading...' first
};
💡 Use requestAnimationFrame for visual updates — it runs just before the browser paints, ensuring 60fps synchronization. setTimeout(0) lets the browser render but has no frame alignment.
Practice this question →
Q16Advanced

What are Environment Records and how do they underpin scope?

💡 Hint: The data structure that stores variable bindings in each scope — the "real" scope implementation

An Environment Record is the actual data structure that stores identifier (variable) bindings for a scope. When the spec says "scope," it means Environment Records under the hood.

// Each scope = one Environment Record created at runtime
// Environment Record stores { variable: value } mappings

function outer() {
  let x = 1;     // x stored in outer's Environment Record
  function inner() {
    let y = 2;   // y stored in inner's Environment Record
    console.log(x); // looks up outer's Environment Record via [[OuterEnv]]
  }
  inner();
}
// Call stack at inner():
// inner's ER: { y: 2, [[OuterEnv]] → outer's ER }
// outer's ER: { x: 1, inner: fn, [[OuterEnv]] → global ER }
// global ER:  { outer: fn, ... }

// Types of Environment Records:
// - Declarative ER: let, const, function declarations, parameters
// - Object ER: var declarations and global code (backed by an object)
// - Global ER: combination of both (global scope)
// - Module ER: ES module scope
// - Function ER: function body scope

// Closures = an inner function holding a reference to
// the outer function's Environment Record AFTER it has returned
const fn = outer(); // outer's ER stays alive because inner references it
💡 Environment Records replaced the older "Activation Object" spec term. Understanding them demystifies closures completely: a closure is just a function holding a reference to an outer Environment Record.
Practice this question →
Q17Advanced

What is the new Function() constructor and when is it used?

💡 Hint: Create functions from strings at runtime — dynamic but slower, eval-like risks, no closure
// Syntax: new Function([...params], functionBody)
const add = new Function('a', 'b', 'return a + b');
add(2, 3); // 5

const greet = new Function('name', 'return "Hello, " + name');
greet('Alice'); // 'Hello, Alice'

// Key characteristic: no closure — runs in GLOBAL scope
const x = 10;
function test() {
  const x = 20; // local x
  const fn = new Function('return x'); // does NOT close over local x
  return fn();
}
test(); // 10 (global x) — NOT 20!

// Use cases (rare):
// 1. Dynamic code from server (CMS, user-defined formulas)
const formula = new Function('a', 'b', serverSideFormula);

// 2. Template engines (pre-compile to JS)

// 3. Sandboxed evaluation (safer than eval)
const fn = new Function('data', 'with(data) { return x + y }');
fn({ x: 1, y: 2 }); // 3 (limited scope)

Risks and drawbacks: Cannot access local scope (can't make closures). Security risk if body comes from user input. Blocks V8 optimization. Slower than regular functions.

💡 Think of new Function() as eval() for function bodies. Only use for dynamic code execution from trusted sources. In CSP-hardened apps, new Function() is blocked along with eval.
Practice this question →

Other JavaScript Interview Topics

FunctionsAsync JSObjectsArrays'this' KeywordError HandlingModern JSPerformanceDOM & EventsBrowser APIs

Ready to practice Core JS?

Get AI feedback on your answers, predict code output, and fix real bugs.

Start Free Practice →