MindForge
MindForge
DashboardHelp & Support
Dashboard
MindForge

Interview Preparation Bootcamp

Course progress

0%
0 of 177 modules complete
Start Here0/1
  • How to Use This Course
Data Structures & Algorithms0/27
  • Arrays and Hashing
  • Two Pointers
  • Sliding Window
  • Sliding Window: Harder Problems
  • Binary Search
  • Stacks
  • Practice Test: Arrays, Pointers, Windows, Search and Stacks
  • Trees: Traversals and Core Patterns
  • Binary Search Trees and Rebuilding a Tree
  • Tries (Prefix Trees)
  • Heaps and Priority Queues
  • Graphs: BFS and DFS
  • Topological Sort and Cycle Detection
  • Union-Find (Disjoint Set Union)
  • Practice Test: Trees, Heaps and Graphs
  • Dynamic Programming Basics
  • Dynamic Programming: Two Dimensions
  • Dynamic Programming: Hard String Matching
  • Backtracking
  • Backtracking: Constraint Satisfaction
  • Greedy Algorithms
  • Interval Problems
  • Bit Manipulation
  • Math and Geometry
  • String Manipulation
  • Pattern Recall and Mock Coding Rounds
  • Practice Test: DP, Backtracking, Greedy and More

System Design

Foundations0/16
  • The System Design Interview Framework
  • Back-of-the-Envelope Estimation
  • Networking and Protocols for Design
  • API Design and Idempotency
  • Load Balancing, Proxies, and Scaling Out
  • Caching and CDNs
  • Choosing a Database: Storage, Indexes and Transactions
  • Replication, Partitioning, and Sharding
  • CAP, PACELC, and Levels of Consistency
  • Messaging, Queues, and Event-Driven Architecture
  • Distributed Transactions and Data Integrity
  • Coordination, Consensus, and Distributed Locks
  • Reliability, Resilience, and Rate Limiting
  • Observability, Security, and Cost
  • System Design Cheat Sheet
  • Practice Test: System Design Foundations
Case Studies0/23
  • Design a URL Shortener
  • Design a Rate Limiter
  • Design a Notification Service
  • Design a Job Queue System
  • Design a Chat App (WhatsApp)
  • Design an Analytics Pipeline
  • Design a Multi-tenant SaaS Architecture
  • Design a Social Media Feed (Twitter/X)
  • Design a Search Autocomplete System
  • Design a File Storage Service (Dropbox)
  • Design Google Docs
  • Design a Code Review System
  • Design Netflix
  • Design YouTube
  • Design Spotify
  • Design Uber/Lyft
  • Design Airbnb
  • Design Ticketmaster (Eventbrite)
  • Design an E-commerce Cart, Checkout, and Catalog
  • Design a Payment System
  • Design a Logging and Monitoring System
  • Mock Design Rounds
  • Practice Test: System Design Case Studies
Low-Level Design0/19
  • The LLD Interview Framework and UML
  • OOP Foundations for Design
  • SOLID Principles
  • Creational Patterns
  • Structural Patterns
  • Behavioral Patterns I — Strategy, Observer, Command, State, Template Method
  • Behavioral Patterns II and Anti-Patterns
  • Concurrency in Low-Level Design
  • Design a Parking Lot
  • Design an Elevator System
  • Design a Movie Ticket Booking System
  • Design an Expense-Sharing App (Splitwise)
  • Design a Vending Machine and an ATM
  • Design a Rate Limiter and a Logging Framework
  • Design a Notification Service and an In-Memory Cache
  • Design Games — Tic-Tac-Toe, Snake and Ladder, Chess
  • Database Design for LLD
  • LLD Cheat Sheet and Recall Drill
  • Practice Test: Low-Level Design

Backend

Python0/5
  • How Python Runs Your Code
  • Functions Deep Dive: Closures, Generators, Context Managers, Scope
  • Classes Deep Dive: Method Types, MRO, Metaclasses
  • Standard Library and Serialization
  • Practice Test: Python
Django0/6
  • Django Request Lifecycle
  • Lab: Django Middleware — Request ID & Response Time
  • Django ORM and Querysets
  • Django's Cache Framework
  • Practice Test: Django
  • Django in Production: Debugging Labs
FastAPI0/5
  • FastAPI, ASGI, and Async
  • FastAPI Dependency Injection
  • FastAPI Background Tasks
  • Lab: FastAPI Async — Concurrent Aggregation
  • Practice Test: FastAPI
Databases (PostgreSQL)0/6
  • Indexing
  • Query Optimization
  • Transactions and Isolation
  • Migrations and Schema Changes
  • Hierarchical Data and Safe Concurrent Updates
  • Practice Test: Databases
Caching, Queues & Distributed Systems0/8
  • Redis Caching
  • Redis Advanced Patterns
  • Celery and Background Workers
  • Kafka and Message Queues
  • Async Pipelines
  • Distributed Systems Basics
  • Distributed Locks
  • Practice Test: Caching, Queues and Distributed Systems
APIs, Security & Deployment0/9
  • REST API Design
  • API Versioning and Error Handling
  • API Security and Authentication
  • WebSockets and Real-Time Systems
  • Docker and Containers
  • File Storage with S3
  • Testing Strategies
  • Mock Backend Round
  • Practice Test: APIs, Security and Deployment
Frontend0/38
  • HTML Document Structure and Semantics
  • CSS Box Model, Flexbox and Grid
  • CSS Architecture and Theming
  • CSS and Rendering Performance
  • Variables, Scope, Hoisting, and Truthiness
  • Functions, this, and Binding
  • Objects and Prototypal Inheritance
  • JavaScript Utility Patterns: Cloning, Caching, and Memory
  • Promises From Scratch
  • The Event Loop and Browser Rendering Pipeline
  • The DOM: Nodes, Events, and Traversal
  • Practice Test: HTML, CSS and JavaScript
  • React Rendering Fundamentals
  • React Hooks Internals
  • React Performance
  • Lab: React — Stateful Counter Component
  • State Management
  • State Machines in React
  • React Custom Hooks and Composition Patterns
  • TypeScript for React
  • React Testing
  • Practice Test: React
  • HTTP and Caching
  • Network Performance and Web Vitals
  • Code Splitting and Bundles
  • Virtualization
  • GraphQL and Data Fetching
  • Web Security
  • WebSockets and Real-time
  • SSR and Next.js
  • Micro-frontends
  • Build Tools and Bundling
  • Accessibility
  • Error Handling
  • Internationalization
  • Animation
  • Rapid Recall and Mock Frontend Rounds
  • Practice Test: Performance, Architecture and Production
Behavioral & Interview Day0/14
  • Resume and Project Walkthroughs
  • The STAR Method and Refining Your Stories
  • Conflict and Difficult Coworkers
  • Failures, Incidents, and Obstacles
  • Leadership, Mentorship, and Ownership
  • Teamwork, Communication, and Feedback
  • Growth and Handling Ambiguity
  • Career Goals, Why This Company, and Negotiation
  • Situational Questions
  • Company Research
  • Mock Behavioral Rounds
  • Interview Day
  • After the Interview
  • Practice Test: Behavioral and Interview Day
Promises From Scratch
Course contents

Interview Preparation Bootcamp

Course progress

0%
0 of 177 modules complete
Start Here0/1
  • How to Use This Course
Data Structures & Algorithms0/27
  • Arrays and Hashing
  • Two Pointers
  • Sliding Window
  • Sliding Window: Harder Problems
  • Binary Search
  • Stacks
  • Practice Test: Arrays, Pointers, Windows, Search and Stacks
  • Trees: Traversals and Core Patterns
  • Binary Search Trees and Rebuilding a Tree
  • Tries (Prefix Trees)
  • Heaps and Priority Queues
  • Graphs: BFS and DFS
  • Topological Sort and Cycle Detection
  • Union-Find (Disjoint Set Union)
  • Practice Test: Trees, Heaps and Graphs
  • Dynamic Programming Basics
  • Dynamic Programming: Two Dimensions
  • Dynamic Programming: Hard String Matching
  • Backtracking
  • Backtracking: Constraint Satisfaction
  • Greedy Algorithms
  • Interval Problems
  • Bit Manipulation
  • Math and Geometry
  • String Manipulation
  • Pattern Recall and Mock Coding Rounds
  • Practice Test: DP, Backtracking, Greedy and More

System Design

Foundations0/16
  • The System Design Interview Framework
  • Back-of-the-Envelope Estimation
  • Networking and Protocols for Design
  • API Design and Idempotency
  • Load Balancing, Proxies, and Scaling Out
  • Caching and CDNs
  • Choosing a Database: Storage, Indexes and Transactions
  • Replication, Partitioning, and Sharding
  • CAP, PACELC, and Levels of Consistency
  • Messaging, Queues, and Event-Driven Architecture
  • Distributed Transactions and Data Integrity
  • Coordination, Consensus, and Distributed Locks
  • Reliability, Resilience, and Rate Limiting
  • Observability, Security, and Cost
  • System Design Cheat Sheet
  • Practice Test: System Design Foundations
Case Studies0/23
  • Design a URL Shortener
  • Design a Rate Limiter
  • Design a Notification Service
  • Design a Job Queue System
  • Design a Chat App (WhatsApp)
  • Design an Analytics Pipeline
  • Design a Multi-tenant SaaS Architecture
  • Design a Social Media Feed (Twitter/X)
  • Design a Search Autocomplete System
  • Design a File Storage Service (Dropbox)
  • Design Google Docs
  • Design a Code Review System
  • Design Netflix
  • Design YouTube
  • Design Spotify
  • Design Uber/Lyft
  • Design Airbnb
  • Design Ticketmaster (Eventbrite)
  • Design an E-commerce Cart, Checkout, and Catalog
  • Design a Payment System
  • Design a Logging and Monitoring System
  • Mock Design Rounds
  • Practice Test: System Design Case Studies
Low-Level Design0/19
  • The LLD Interview Framework and UML
  • OOP Foundations for Design
  • SOLID Principles
  • Creational Patterns
  • Structural Patterns
  • Behavioral Patterns I — Strategy, Observer, Command, State, Template Method
  • Behavioral Patterns II and Anti-Patterns
  • Concurrency in Low-Level Design
  • Design a Parking Lot
  • Design an Elevator System
  • Design a Movie Ticket Booking System
  • Design an Expense-Sharing App (Splitwise)
  • Design a Vending Machine and an ATM
  • Design a Rate Limiter and a Logging Framework
  • Design a Notification Service and an In-Memory Cache
  • Design Games — Tic-Tac-Toe, Snake and Ladder, Chess
  • Database Design for LLD
  • LLD Cheat Sheet and Recall Drill
  • Practice Test: Low-Level Design

Backend

Python0/5
  • How Python Runs Your Code
  • Functions Deep Dive: Closures, Generators, Context Managers, Scope
  • Classes Deep Dive: Method Types, MRO, Metaclasses
  • Standard Library and Serialization
  • Practice Test: Python
Django0/6
  • Django Request Lifecycle
  • Lab: Django Middleware — Request ID & Response Time
  • Django ORM and Querysets
  • Django's Cache Framework
  • Practice Test: Django
  • Django in Production: Debugging Labs
FastAPI0/5
  • FastAPI, ASGI, and Async
  • FastAPI Dependency Injection
  • FastAPI Background Tasks
  • Lab: FastAPI Async — Concurrent Aggregation
  • Practice Test: FastAPI
Databases (PostgreSQL)0/6
  • Indexing
  • Query Optimization
  • Transactions and Isolation
  • Migrations and Schema Changes
  • Hierarchical Data and Safe Concurrent Updates
  • Practice Test: Databases
Caching, Queues & Distributed Systems0/8
  • Redis Caching
  • Redis Advanced Patterns
  • Celery and Background Workers
  • Kafka and Message Queues
  • Async Pipelines
  • Distributed Systems Basics
  • Distributed Locks
  • Practice Test: Caching, Queues and Distributed Systems
APIs, Security & Deployment0/9
  • REST API Design
  • API Versioning and Error Handling
  • API Security and Authentication
  • WebSockets and Real-Time Systems
  • Docker and Containers
  • File Storage with S3
  • Testing Strategies
  • Mock Backend Round
  • Practice Test: APIs, Security and Deployment
Frontend0/38
  • HTML Document Structure and Semantics
  • CSS Box Model, Flexbox and Grid
  • CSS Architecture and Theming
  • CSS and Rendering Performance
  • Variables, Scope, Hoisting, and Truthiness
  • Functions, this, and Binding
  • Objects and Prototypal Inheritance
  • JavaScript Utility Patterns: Cloning, Caching, and Memory
  • Promises From Scratch
  • The Event Loop and Browser Rendering Pipeline
  • The DOM: Nodes, Events, and Traversal
  • Practice Test: HTML, CSS and JavaScript
  • React Rendering Fundamentals
  • React Hooks Internals
  • React Performance
  • Lab: React — Stateful Counter Component
  • State Management
  • State Machines in React
  • React Custom Hooks and Composition Patterns
  • TypeScript for React
  • React Testing
  • Practice Test: React
  • HTTP and Caching
  • Network Performance and Web Vitals
  • Code Splitting and Bundles
  • Virtualization
  • GraphQL and Data Fetching
  • Web Security
  • WebSockets and Real-time
  • SSR and Next.js
  • Micro-frontends
  • Build Tools and Bundling
  • Accessibility
  • Error Handling
  • Internationalization
  • Animation
  • Rapid Recall and Mock Frontend Rounds
  • Practice Test: Performance, Architecture and Production
Behavioral & Interview Day0/14
  • Resume and Project Walkthroughs
  • The STAR Method and Refining Your Stories
  • Conflict and Difficult Coworkers
  • Failures, Incidents, and Obstacles
  • Leadership, Mentorship, and Ownership
  • Teamwork, Communication, and Feedback
  • Growth and Handling Ambiguity
  • Career Goals, Why This Company, and Negotiation
  • Situational Questions
  • Company Research
  • Mock Behavioral Rounds
  • Interview Day
  • After the Interview
  • Practice Test: Behavioral and Interview Day
Browsing without an account — progress is saved in this browser only.Sign in to save it

Promises From Scratch

Reading35 min

"Implement Promise from scratch" is the natural next step after call/apply/bind: the same "prove you understand the primitive, not just the API" question, one level up. This lesson builds a working Promise, then, catch, finally, resolve, reject, all, race, allSettled, any, using only closures, no class, no this.

One fact that explains a lot of confusing behavior later: the executor runs synchronously

The function you pass to new Promise((resolve, reject) => {...}) runs immediately, the instant the constructor is called, not later, not on a microtask. If that executor calls resolve synchronously, the promise is already fulfilled before new Promise(...) even finishes returning. Hold onto this fact; it is what makes the trace at the end of this lesson make sense.

Remember: the executor function runs the moment new Promise(...) is called, synchronously, before the line even finishes evaluating.

Sign in to try this knowledge check.

Why closures, not a class

A class-based Promise would store state/value/callbacks as this.state, immediately reopening every this-binding footgun from the functions and binding lessons: a detached method loses its this. A closure-based implementation sidesteps that entirely. The state lives in the enclosing function's own scope, captured by every inner function regardless of how those functions later get called or passed around. This is the same closure-versus-this trade-off interviewers are probing for when they ask you to build this without class.

Remember: a closure-based implementation shares state through the enclosing function's scope. A class-based one would reopen every this-binding footgun a detached method call can hit.

Sign in to try this knowledge check.

The core implementation

JavaScript
function myPromise(executor) {
  let state = 'pending'; // pending | fulfilled | rejected
  let value = undefined;
  let callbacks = []; // queued { onFulfilled, onRejected, resolveNext, rejectNext }

  function resolve(val) {
    if (val && typeof val.then === 'function') {
      val.then(resolve, reject); // resolved with a thenable — chain into it instead
      return;
    }
    if (state !== 'pending') return; // settling is permanent: a second call is a silent no-op
    state = 'fulfilled';
    value = val;
    flush();
  }

  function reject(reason) {
    if (state !== 'pending') return;
    state = 'rejected';
    value = reason;
    flush();
  }

  function flush() {
    queueMicrotask(() => {
      callbacks.forEach(cb => handleCallback(cb));
      callbacks = [];
    });
  }

  function handleCallback(cb) {
    const { onFulfilled, onRejected, resolveNext, rejectNext } = cb;
    try {
      if (state === 'fulfilled') {
        resolveNext(typeof onFulfilled === 'function' ? onFulfilled(value) : value);
      } else if (state === 'rejected') {
        if (typeof onRejected === 'function') resolveNext(onRejected(value));
        else rejectNext(value);
      }
    } catch (err) {
      rejectNext(err);
    }
  }

  function then(onFulfilled, onRejected) {
    return myPromise((resolveNext, rejectNext) => {
      const cb = { onFulfilled, onRejected, resolveNext, rejectNext };
      if (state === 'pending') callbacks.push(cb);
      else queueMicrotask(() => handleCallback(cb));
    });
  }

  function catchFn(onRejected) { return then(null, onRejected); }
  function finallyFn(onFinally) {
    return then(
      val => { onFinally(); return val; },
      err => { onFinally(); throw err; },
    );
  }

  try {
    executor(resolve, reject);
  } catch (err) {
    reject(err); // a synchronous throw in the executor auto-converts to a rejection
  }

  return { then, catch: catchFn, finally: finallyFn };
}

Walking the pieces: state lives in closure scope, so every inner function shares it by reference with no this involved anywhere. resolve unwraps thenables: if you resolve with another promise, or anything with a .then method, it chains into that instead of treating it as a final value, a simplified version of the spec's actual Promise Resolution Procedure. Callbacks queue while the promise is pending. then always returns a new myPromise, which is what makes .then().then().then() chaining possible: each call hands back a fresh promise that settles based on whatever the previous handler returned or threw. queueMicrotask schedules the async part: real Promise callbacks never run synchronously, even if the promise was already settled when .then() was called, and the implementation above deliberately still wraps that already-settled path in queueMicrotask rather than calling handleCallback immediately. Skip that and .then() would sometimes run synchronously and sometimes not, depending purely on timing, exactly the class of bug this guarantee exists to prevent.

Remember: .then() always returns a new promise, and it is always deferred to a microtask, even if the original promise already settled.

Sign in to try this knowledge check.

Chaining: why then always needs to return a new promise

.then() can be called before a promise settles, the async case, waiting inside a setTimeout, or after it already has, the sync case. Both need handling: an already-settled call runs the callback right away; a still-pending call pushes it onto a queue that resolve/reject will drain once the state changes. For chaining specifically, the callback's return value has to become the next promise's resolved value if it returns a plain value, or the next promise's rejection if it throws, mirroring exactly how a synchronous try/catch propagates. That is what lets an error thrown three .then() calls deep still land in a single .catch() at the very end of the chain.

Remember: a handler's return value becomes the next promise's resolved value; a thrown error becomes the next promise's rejection. That's what lets one .catch() at the end catch an error from several .then() calls back.

Sign in to try this knowledge check.

Static methods: resolve, reject, all, race

JavaScript
myPromise.resolve = function (val) { return myPromise(resolve => resolve(val)); };
myPromise.reject = function (reason) { return myPromise((_, reject) => reject(reason)); };

myPromise.all = function (promises) {
  return myPromise((resolve, reject) => {
    const results = [];
    let completed = 0;
    if (promises.length === 0) return resolve([]);
    promises.forEach((p, i) => {
      myPromise.resolve(p).then(val => {
        results[i] = val;
        completed++;
        if (completed === promises.length) resolve(results);
      }, reject);
    });
  });
};

myPromise.race = function (promises) {
  return myPromise((resolve, reject) => {
    promises.forEach(p => myPromise.resolve(p).then(resolve, reject));
  });
};

all rejects the instant any one promise rejects, because .then(val => {...}, reject) wires every individual promise's rejection straight to the outer promise's reject, and the first rejection to fire wins: resolve/reject are no-ops after the first call, thanks to the state !== 'pending' guard earlier. The other promises in the array keep running, they are still .then()-ed, all just stops caring about their eventual results once it has already settled.

Remember: Promise.all rejects on the very first rejection, but the other promises keep running in the background regardless.

Sign in to try this knowledge check.

The other two combinators, and where each one actually settles

Promise.all, .race, .allSettled, and .any all sound similar and get mixed up constantly. The difference is exactly what condition ends the wait:

Combinator Settles when Empty array
all any one rejects, or every one resolves resolves []
race the very first promise settles, win or lose stays pending forever
allSettled always waits for every promise, regardless of outcome resolves []
any the first promise resolves rejects with an AggregateError
JavaScript
function allSettled(promises) {
  const result = [];
  let count = 0;
  return new Promise((resolve) => {
    if (promises.length === 0) return resolve([]);
    promises.forEach((promise, index) => {
      promise
        .then(value => { result[index] = { status: "fulfilled", value }; })
        .catch(reason => { result[index] = { status: "rejected", reason }; })
        .finally(() => { count++; if (count === promises.length) resolve(result); });
    });
  });
}

allSettled never fails fast: it waits for every promise no matter the outcome, recording a fulfilled or rejected entry for each. .finally() does the real work here, incrementing the shared counter on both the resolve and reject branches, so that bookkeeping does not need to be duplicated in each .then()/.catch() separately.

JavaScript
function any(promises) {
  const errors = [];
  let count = 0;
  return new Promise((resolve, reject) => {
    if (promises.length === 0) return reject(new AggregateError([], "All promises were rejected"));
    promises.forEach((promise, index) => {
      promise
        .then(value => resolve(value))
        .catch(reason => { errors[index] = reason; })
        .finally(() => { count++; if (count === promises.length) reject(new AggregateError(errors)); });
    });
  });
}

The key fact that makes this implementation safe: once a promise settles, calling resolve or reject on it again is a silent no-op. If one promise resolves early, resolve(value) fires once and wins; even if count later reaches promises.length and the code tries to reject, that second call does nothing at all, because the promise already settled. race answers "whoever finishes first, win or lose." any answers "give me the first success, and only give up once nothing succeeded."

Remember: race cares who finishes first, win or lose. any cares only about the first success, and only gives up once everything has failed.

Sign in to try this knowledge check.

Tracing the output order

tsx
async function asyncOrder() {
  console.log('A: start');
  await Promise.resolve();
  console.log('B: after await');
}

console.log('start');
asyncOrder();
console.log('end');

// Output: start, A: start, end, B: after await

Everything in asyncOrder before the await runs synchronously, the instant the function is called, exactly like the executor fact this lesson opened with. The code after await is scheduled as a microtask continuation, equivalent to Promise.resolve().then(() => { ...rest of function... }), which is why end, still on the synchronous call stack, prints before B: after await, deferred to the microtask queue, even though asyncOrder() was called before the final console.log('end') in the source.

Remember: everything before an await runs synchronously. Everything after it is a microtask, scheduled to run once the current synchronous code finishes.

PreviousJavaScript Utility Patterns: Cloning, Caching, and MemoryNextThe Event Loop and Browser Rendering Pipeline

Course progress

0%
0 of 177 modules complete

On this page

  • One fact that explains a lot of confusing behavior later: the executor runs synchronously
  • Why closures, not a class
  • The core implementation
  • Chaining: why then always needs to return a new promise
  • Static methods: resolve, reject, all, race
  • The other two combinators, and where each one actually settles
  • Tracing the output order