Various Code Examples And Patterns

call, bind, and apply

JavaScript’s call(), bind(), and apply() methods are powerful tools for controlling function context (this) at runtime. They are especially useful when borrowing methods, delaying execution, or separating data from behavior.

These methods are commonly used when:

This section also demonstrates how arrow functions behave differently with this, and includes a custom implementation of Function.prototype.bind.

Example: Losing and Rebinding Context

This example demonstrates what happens when a method loses its context (this) and how to recover it using call() and bind():

const counter = {
  value: 10,
  add(num) {
    this.value += num;
    return this.value;
  },
};
 
function operate(method, num) {
  return method(num);
}
 
function operateWithCall(method, num) {
  return method.call(counter, num);
}
 
console.log(counter.add(1)); // 11
console.log(operateWithCall(counter.add, 1)); // 12
console.log(operate(counter.add.bind(counter), 1)); // 13
try {
  console.log(operate(counter.add, 1));
} catch (e) {
  console.error(e.message);
  // Cannot read properties of undefined (reading 'value')
}

Example 1: Using call and apply to Change Context

This example shows how call() and apply() can be used to invoke a method with a different this context:

const objA = {
  data: "I am from objA",
  print() {
    console.log(this.data);
  },
};
 
const objB = {
  data: "I am from objB",
};
 
objA.print.call(objB); // I am from objB
objA.print.apply(objB); // I am from objB

Example 2: Preserving Method Context with setTimeout

This example shows how to preserve the this context when calling a method asynchronously using setTimeout.

The delayMethod function returns a wrapper that defers method execution by 1 second while ensuring that this still refers to the original object.

function delayMethod(obj, methodName) {
  return function (...args) {
    setTimeout(() => obj[methodName](...args), 1000);
  };
}
 
const user = {
  name: "Alice",
  greet(greeting) {
    console.log(`${greeting}, ${this.name}!`);
  },
};
 
const delayedGreet = delayMethod(user, "greet");
delayedGreet("Hello"); // After 1 second: "Hello, Alice!"

Example 3: Custom bind() Implementation

This example demonstrates how to implement a simplified version of Function.prototype.bind().

The customBind function:

This mimics native bind() behavior, including partial application of arguments.

Function.prototype.customBind = function (context, ...args) {
  const originalFunction = this;
  return function (...newArgs) {
    return originalFunction.apply(context, [...args, ...newArgs]);
  };
};
 
function greet(greeting, punctuation) {
  console.log(`${greeting}, ${this.name}${punctuation}`);
}
 
const person = { name: "John" };
const boundGreet = greet.customBind(person, "Hello");
boundGreet("!"); // "Hello, John!"

Example 4: Sharing Methods Across Objects with call()

In this example, the calculateTotal method from store1 is reused for store2 by using Function.prototype.call().

const store1 = {
  taxRate: 0.1,
  calculateTotal(price, quantity) {
    const subtotal = price * quantity;
    return subtotal + subtotal * this.taxRate;
  },
};
 
const store2 = {
  taxRate: 0.2,
};
 
const price = 50;
const quantity = 2;
 
// Calculate total for `store2` using the method from `store1`
const total = store1.calculateTotal.call(store2, price, quantity);
console.log(total); // 120 (price * quantity + tax)
 
// 2 solution
store2.calculateTotal = store1.calculateTotal;
console.log(store2.calculateTotal(price, quantity));

Example 5: bind() for Context in Rate-Limited Function

This example demonstrates how to use bind() to ensure the correct this context when passing an object's method to a wrapper function.

function rateLimit(fn, ms) {
  let lastCall = 0;
 
  return function (...args) {
    const now = Date.now();
 
    if (now - lastCall < ms) return;
    lastCall = now;
    return fn(...args);
  };
}
 
const logger = {
  message: "Rate limited log",
  log() {
    console.log(this.message);
  },
};
 
const rateLimitedLog = rateLimit(logger.log.bind(logger), 1000);
rateLimitedLog(); // Rate limited log
rateLimitedLog();
setTimeout(rateLimitedLog, 1500);
// Logs "Rate limited log" after 1.5s

Example 6: Using bind() to Preserve this

In this example, greet() is a regular function that relies on this.name. When it's not called in the context of an object, this would be undefined.

To ensure the correct context (person), we create a bound version of the function using bind(person).

This guarantees that this.name inside greet() always refers to "John", even if called independently.

const person = {
  name: "John",
};
 
function greet() {
  console.log(`Hello, ${this.name}`);
}
 
// Create a bound function
const boundGreet = greet.bind(person);
boundGreet(); // Hello, John

Example 7: Arrow Functions Ignore call

In this example, the arrow function is defined inside the greet() method using an arrow function.

Arrow functions do not have their own this — they inherit this from their enclosing lexical context. So even when using call() with a different this value ({ name: "Bob" }), the arrow function still uses this.name from the outer greet() context, which is user.

Thus, the output is: Hello, Alice.

const user = {
  name: "Alice",
  greet() {
    const arrow = () => console.log(`Hello, ${this.name}`);
    arrow.call({ name: "Bob" }); // Arrow functions ignore `call`
  },
};
 
user.greet(); // Hello, Alice

Example 8: Preserving this in Callbacks

When passing a method like user.logName to another function (e.g. executeCallback), the this context is lost unless it is explicitly preserved.

Both approaches ensure the correct this context, so the output is: Alice.

const user = {
  name: "Alice",
  logName() {
    console.log(this.name);
  },
};
 
function executeCallback(callback) {
  callback();
}
 
// Preserve context with `bind`
executeCallback(user.logName.bind(user)); // Alice
executeCallback(() => user.logName()); // Alice

clsx

The clsx utility is commonly used to conditionally join class names in JavaScript and React projects.

This custom implementation mimics the behavior of the clsx library by:

This allows for clean, readable class name composition in UI code.

function clsx(...args) {
  const classes = [];
 
  for (const arg of args) {
    // Skip the current iteration if the argument is falsy
    if (!arg) continue;
 
    if (typeof arg === "string") {
      classes.push(arg);
    } else if (Array.isArray(arg)) {
      classes.push(clsx(...arg)); // Recursively process arrays
    } else if (typeof arg === "object") {
      for (const key in arg) {
        if (arg[key]) {
          classes.push(key); // Push key if value is truthy
        }
      }
    }
  }
 
  return classes.join(" "); // Join classes with a space
}
 
console.log(
  clsx("base-class", { active: true, disabled: false }, [
    "additional-class",
    "another-class",
  ]),
); // base-class active additional-class another-class
console.log(
  clsx(null, false, "bar", undefined, { baz: null }, "", [[[{ one: 1 }]]]),
); // bar one

contains duplicate

This function checks if a given array contains any duplicate values.

It uses a Set to track elements that have already been seen while iterating through the array. If a value is found in the Set, the function returns true, indicating a duplicate. If no duplicates are found by the end of iteration, it returns false.

function containsDuplicate(nums) {
  const seen = new Set();
 
  for (const num of nums) {
    if (seen.has(num)) {
      return true;
    }
 
    seen.add(num);
  }
 
  return false;
}
 
console.log(containsDuplicate([1, 1, 3, 4])); // true
console.log(containsDuplicate([1, 3, 4])); // false

deep clone

The deepClone function recursively copies all levels of an object or array, creating a fully independent clone. This avoids shared references and ensures that changes to nested structures in the cloned object won’t affect the original.

It handles:

Perfect for deep copying JSON-compatible data without using JSON.parse(JSON.stringify(...)), which fails on special types like Date, Map, Set, or circular references.

function deepClone(obj) {
  if (obj === null || typeof obj !== "object") {
    return obj;
  }
 
  if (Array.isArray(obj)) {
    // Recursively clone array elements
    return obj.map(deepClone);
  }
 
  const clonedObj = {};
  for (const key in obj) {
    if (obj.hasOwnProperty(key)) {
      // Recursively clone object properties
      clonedObj[key] = deepClone(obj[key]);
    }
  }
 
  return clonedObj;
}
 
const obj = { a: 1, b: [{ c: 2, d: 3 }] };
const clonedObj = deepClone(obj);
console.log(clonedObj); // { a: 1, b: [ { c: 2, d: 3 } ] }

dictionary of nested arrays

This example demonstrates how to transform a deeply nested array structure (e.g., categories → subcategories → items) into a dictionary (object-based) format for fast lookup by id.

Benefits:

Approaches Covered:

Great for building normalized state for UIs, lookup tables, or improving nested data traversal.

const data = [
  {
    id: 1,
    name: "Category A",
    items: [
      {
        id: 2,
        name: "Subcategory A1",
        items: [
          { id: 3, name: "Item A1-1", value: 10 },
          { id: 4, name: "Item A1-2", value: 15 },
        ],
      },
      {
        id: 5,
        name: "Subcategory A2",
        items: [
          { id: 6, name: "Item A2-1", value: 20 },
          { id: 7, name: "Item A2-2", value: 25 },
        ],
      },
    ],
  },
  {
    id: 8,
    name: "Category B",
    items: [
      {
        id: 9,
        name: "Subcategory B1",
        items: [
          { id: 10, name: "Item B1-1", value: 30 },
          { id: 11, name: "Item B1-2", value: 35 },
        ],
      },
      {
        id: 12,
        name: "Subcategory B2",
        items: [
          { id: 13, name: "Item B2-1", value: 40 },
          { id: 14, name: "Item B2-2", value: 45 },
        ],
      },
    ],
  },
];
 
function createNestedDictionary(data) {
  const dictionary = {};
 
  for (const category of data) {
    dictionary[category.id] = { ...category, subcategories: {} };
 
    for (const subcategory of category.items) {
      dictionary[category.id].subcategories[subcategory.id] = {
        ...subcategory,
        items: {},
      };
 
      for (const item of subcategory.items) {
        dictionary[category.id].subcategories[subcategory.id].items[item.id] =
          item;
      }
    }
  }
 
  return dictionary;
}
 
const nestedDictionary = createNestedDictionary(data);
 
console.log(nestedDictionary[1].name); // Category A
console.log(nestedDictionary[1].subcategories[5].name); // Subcategory A2
console.log(nestedDictionary[1].subcategories[5].items[7].name); // Item A2-2
console.log(nestedDictionary[8].subcategories[12].items[14]);
// { id: 14, name: 'Item B2-2', value: 45 }
const data = [
  {
    id: 1,
    name: "Category A",
    items: [
      {
        id: 2,
        name: "Subcategory A1",
        items: [
          { id: 3, name: "Item A1-1", value: 10 },
          { id: 4, name: "Item A1-2", value: 15 },
        ],
      },
      {
        id: 5,
        name: "Subcategory A2",
        items: [
          { id: 6, name: "Item A2-1", value: 20 },
          { id: 7, name: "Item A2-2", value: 25 },
        ],
      },
    ],
  },
  {
    id: 8,
    name: "Category B",
    items: [
      {
        id: 9,
        name: "Subcategory B1",
        items: [
          { id: 10, name: "Item B1-1", value: 30 },
          { id: 11, name: "Item B1-2", value: 35 },
        ],
      },
      {
        id: 12,
        name: "Subcategory B2",
        items: [
          { id: 13, name: "Item B2-1", value: 40 },
          { id: 14, name: "Item B2-2", value: 45 },
        ],
      },
    ],
  },
];
 
function mapToDictionary(data, keys) {
  const [currentKey, ...remainingKeys] = keys;
 
  return data.reduce((acc, item) => {
    acc[item.id] = {
      ...item,
      [currentKey || "items"]: item.items
        ? mapToDictionary(item.items, remainingKeys)
        : undefined,
    };
    return acc;
  }, {});
}
 
const nestedDictionary = mapToDictionary(data, ["subcategories", "items"]);
 
console.log(nestedDictionary[1].name); // Category A
console.log(nestedDictionary[1].subcategories[5].name); // Subcategory A2
console.log(nestedDictionary[1].subcategories[5].items[7].name); // Item A2-2
console.log(nestedDictionary[8].subcategories[9].items[11]);
// { id: 11, name: 'Item B1-2', value: 35, items: undefined }

typescript

type Item = {
  id: number;
  name: string;
  value: number;
};
 
type Subcategory = {
  id: number;
  name: string;
  items: Item[];
};
 
type Category = {
  id: number;
  name: string;
  items: Subcategory[];
};
 
type Subcategories = Record<
  number,
  {
    name: string;
    items: Record<number, Item>;
  }
>;
 
type NestedDictionary = Record<
  number,
  {
    name: string;
    subcategories: Subcategories;
  }
>;
 
function mapItemsToDictionary(items: Item[]) {
  return items.reduce(
    (acc, item) => {
      acc[item.id] = item;
      return acc;
    },
    {} as Record<number, Item>,
  );
}
 
function mapSubcategoriesToDictionary(
  subcategories: Subcategory[],
) {
  return subcategories.reduce((acc, subcategory) => {
    acc[subcategory.id] = {
      name: subcategory.name,
      items: mapItemsToDictionary(subcategory.items),
    };
    return acc;
  }, {} as Subcategories);
}
 
function createNestedDictionary(
  categories: Category[],
): NestedDictionary {
  return categories.reduce((acc, category) => {
    acc[category.id] = {
      name: category.name,
      subcategories: mapSubcategoriesToDictionary(
        category.items,
      ),
    };
    return acc;
  }, {} as NestedDictionary);
}
 
const data: Category[] = [
  {
    id: 1,
    name: "Category A",
    items: [
      {
        id: 2,
        name: "Subcategory A1",
        items: [
          { id: 3, name: "Item A1-1", value: 10 },
          { id: 4, name: "Item A1-2", value: 15 },
        ],
      },
      {
        id: 5,
        name: "Subcategory A2",
        items: [
          { id: 6, name: "Item A2-1", value: 20 },
          { id: 7, name: "Item A2-2", value: 25 },
        ],
      },
    ],
  },
  {
    id: 8,
    name: "Category B",
    items: [
      {
        id: 9,
        name: "Subcategory B1",
        items: [
          { id: 10, name: "Item B1-1", value: 30 },
          { id: 11, name: "Item B1-2", value: 35 },
        ],
      },
      {
        id: 12,
        name: "Subcategory B2",
        items: [
          { id: 13, name: "Item B2-1", value: 40 },
          { id: 14, name: "Item B2-2", value: 45 },
        ],
      },
    ],
  },
];
 
const nestedDictionary = createNestedDictionary(data);
 
console.log(nestedDictionary[1].name); // Category A
console.log(nestedDictionary[1].subcategories[5].name); // Subcategory A2
console.log(
  nestedDictionary[1].subcategories[5].items[7].name,
); // Item A2-2
console.log(
  nestedDictionary[8].subcategories[12].items[14].value,
); // 45

This utility filters a list of objects based on a related property found in another array — similar to a join + filter operation.

In this example, we filter items in the objects array based on the class value of their related object_type, which is matched against the object_types array.

Highlights:

const objects = [
  { id: 1, name: "Test 1", object_type: 1 },
  { id: 2, name: "Test 2", object_type: 1 },
  { id: 3, name: "Test 3", object_type: 2 },
  { id: 4, name: "Test 4", object_type: 3 },
];
 
const object_types = [
  { id: 1, class: "orange" },
  { id: 2, class: "orange" },
  { id: 3, class: "apple" },
  { id: 4, class: "cheese" },
];
 
const groupBy = <T, K extends string | number | symbol>(
  arr: T[],
  callback: (item: T) => K,
): Record<K, T[]> => {
  return arr.reduce(
    (acc: Record<K, T[]>, item: T) => {
      const key = callback(item);
      if (!acc[key]) acc[key] = [];
      acc[key].push(item);
 
      return acc;
    },
    {} as Record<K, T[]>,
  );
};
 
const filterObjectsByClass = <T>(
  cls: string,
  objects: (T & { object_type: number })[],
  objectTypes: { id: number; class: string }[],
): T[] => {
  const result: T[] = [];
 
  const objTypesByClass = groupBy(
    objectTypes,
    (item) => item.class,
  );
 
  for (const item of objects) {
    if (
      objTypesByClass[cls].find(
        (objectType) => objectType.id === item.object_type,
      )
    ) {
      result.push(item);
    }
  }
 
  return result;
};
 
const filteredObjects = filterObjectsByClass(
  "orange",
  objects,
  object_types,
);
console.log(filteredObjects);
 
// [
//   { id: 1, name: 'Test 1', object_type: 1 },
//   { id: 2, name: 'Test 2', object_type: 1 },
//   { id: 3, name: 'Test 3', object_type: 2 }
// ]

filterMap

The filterMap() utility combines filtering and mapping into a single reduce() pass — making it more efficient than chaining .filter().map().

This function takes:

export const filterMap = (array, filterBoolean, mapCallback) => {
  return array.reduce((acc, item, idx) => {
    if (filterBoolean(item)) {
      acc.push(mapCallback(item, idx));
    }
    return acc;
  }, []);
};
 
const people = [
  { name: "Alice", age: 25, active: true },
  { name: "Bob", age: 30, active: false },
  { name: "Charlie", age: 35, active: true },
];
 
const activeNames = filterMap(
  people,
  (person) => person.active,
  (person) => person.name,
);
 
console.log(activeNames); // ['Alice', 'Charlie']

flat nested

Transforms deeply nested category-subcategory-item data into a flat array where each item contains metadata about its parent subcategory and category.

You can do it manually with loops or concisely with flatMap().

const data = [
  {
    id: 1,
    name: "Category A",
    items: [
      {
        id: 2,
        name: "Subcategory A1",
        items: [
          { id: 3, name: "Item A1-1", value: 10 },
          { id: 4, name: "Item A1-2", value: 15 },
        ],
      },
      {
        id: 5,
        name: "Subcategory A2",
        items: [
          { id: 6, name: "Item A2-1", value: 20 },
          { id: 7, name: "Item A2-2", value: 25 },
        ],
      },
    ],
  },
  {
    id: 8,
    name: "Category B",
    items: [
      {
        id: 9,
        name: "Subcategory B1",
        items: [
          { id: 10, name: "Item B1-1", value: 30 },
          { id: 11, name: "Item B1-2", value: 35 },
        ],
      },
      {
        id: 12,
        name: "Subcategory B2",
        items: [
          { id: 13, name: "Item B2-1", value: 40 },
          { id: 14, name: "Item B2-2", value: 45 },
        ],
      },
    ],
  },
];
 
function transformItems(data) {
  const transformedItems = [];
 
  for (const category of data) {
    for (const subcategory of category.items) {
      for (const item of subcategory.items) {
        transformedItems.push({
          id: item.id,
          name: item.name,
          value: item.value,
          subcategory: subcategory.name,
          category: category.name,
        });
      }
    }
  }
 
  return transformedItems;
}
 
const itemsWithCategories = transformItems(data);
 
console.log(itemsWithCategories);
 
// [
//   {
//     id: 3,
//     name: "Item A1-1",
//     value: 10,
//     subcategory: "Subcategory A1",
//     category: "Category A",
//   },
//   {
//     id: 4,
//     name: "Item A1-2",
//     value: 15,
//     subcategory: "Subcategory A1",
//     category: "Category A",
//   },
//   {
//     id: 6,
//     name: "Item A2-1",
//     value: 20,
//     subcategory: "Subcategory A2",
//     category: "Category A",
//   },
//   {
//     id: 7,
//     name: "Item A2-2",
//     value: 25,
//     subcategory: "Subcategory A2",
//     category: "Category A",
//   },
//   {
//     id: 10,
//     name: "Item B1-1",
//     value: 30,
//     subcategory: "Subcategory B1",
//     category: "Category B",
//   },
//   {
//     id: 11,
//     name: "Item B1-2",
//     value: 35,
//     subcategory: "Subcategory B1",
//     category: "Category B",
//   },
//   {
//     id: 13,
//     name: "Item B2-1",
//     value: 40,
//     subcategory: "Subcategory B2",
//     category: "Category B",
//   },
//   {
//     id: 14,
//     name: "Item B2-2",
//     value: 45,
//     subcategory: "Subcategory B2",
//     category: "Category B",
//   },
// ];
 
function transformItems2(data) {
  return data.flatMap((category) =>
    category.items.flatMap((subcategory) =>
      subcategory.items.map((item) => ({
        id: item.id,
        name: item.name,
        value: item.value,
        subcategory: subcategory.name,
        category: category.name,
      })),
    ),
  );
}
 
const itemsWithCategories2 = transformItems2(data);
console.log(itemsWithCategories2);
 
// ...same result

group list by quarters

Groups monthly financial data by calendar quarters using the getQuarter() method from the date-fns library. Each group includes individual monthly entries and an optional cumulative total for the quarter.

Useful for:

Steps:

  1. groupDataByQuarter(data) – Organizes data into a dictionary using keys like "2023-Q1" based on the date field.
  2. initializeDates(groupedQuarters) – Prepares a simplified structure listing quarters and associated month strings.
  3. initializeRestData(groupedQuarters) – Flattens the quarterly structure into tax-specific groups, returning an array of metrics (profit, profit_percent) with their per-quarter values and totals.

This modular approach is helpful when formatting data for analytics dashboards or quarterly reports.

import { getQuarter, parseISO } from "date-fns";
 
// data
export const TaxEntitiesKeys = {
  profit: "profit",
  profit_percent: "profit_percent",
};
 
const mockTaxes = {
  cumulative: [
    { date: "2023-01-31", profit: 1000, profit_percent: 100 },
    { date: "2023-02-28", profit: 2000, profit_percent: 200 },
    { date: "2023-04-30", profit: 3000, profit_percent: 250 },
  ],
  report: [
    { date: "2023-01-31", profit: 300, profit_percent: 30 },
    { date: "2023-02-28", profit: 500, profit_percent: 50 },
    { date: "2023-04-30", profit: 700, profit_percent: 70 },
  ],
};
// data
 
// group by quarters
const groupDataByQuarter = (data) => {
  const quarters = {};
 
  // Group data into quarters based on their date
  for (const monthData of data.report) {
    const date = parseISO(monthData.date);
    const year = date.getFullYear();
    const quarter = getQuarter(date);
    const key = `${year}-Q${quarter}`;
 
    if (!quarters[key]) {
      quarters[key] = { months: [], total: null };
    }
    quarters[key].months.push(monthData);
  }
 
  // Assign cumulative totals to their respective quarters
  for (const totalData of data.cumulative) {
    const date = parseISO(totalData.date);
    const year = date.getFullYear();
    const quarter = getQuarter(date);
    const key = `${year}-Q${quarter}`;
 
    if (quarters[key]) {
      quarters[key].total = totalData;
    }
  }
 
  return quarters;
};
 
const groupedQuarters = groupDataByQuarter(mockTaxes);
console.log(JSON.stringify({ groupedQuarters }, null, 2));
// group by quarters
 
// {
//   groupedQuarters: {
//     "2023-Q1": {
//       months: [
//         {
//           date: "2023-01-31",
//           profit: 300,
//           profit_percent: 30,
//         },
//         {
//           date: "2023-02-28",
//           profit: 500,
//           profit_percent: 50,
//         },
//       ],
//       total: {
//         date: "2023-02-28",
//         profit: 2000,
//         profit_percent: 200,
//       },
//     },
//     "2023-Q2": {
//       months: [
//         {
//           date: "2023-04-30",
//           profit: 700,
//           profit_percent: 70,
//         },
//       ],
//       total: {
//         date: "2023-04-30",
//         profit: 3000,
//         profit_percent: 250,
//       },
//     },
//   },
// };
 
// dates
const initializeDates = (quarters) => {
  const dates = [];
 
  Object.entries(quarters).forEach(([dateKey, data]) => {
    const [year, quarter] = dateKey.split("-Q").map(Number);
 
    let yearEntry = dates.find((entry) => entry.year === year);
    if (!yearEntry) {
      yearEntry = { year, periods: [] };
      dates.push(yearEntry);
    }
 
    yearEntry.periods.push({
      months: data.months.map((month) => month.date),
      quarter: `${quarter}`,
    });
  });
 
  return dates;
};
 
console.log(
  JSON.stringify({ dates: initializeDates(groupedQuarters) }, null, 2),
);
// dates
 
// {
//   dates: [
//     {
//       year: 2023,
//       periods: [
//         {
//           months: ["2023-01-31", "2023-02-28"],
//           quarter: "1",
//         },
//         {
//           months: ["2023-04-30"],
//           quarter: "2",
//         },
//       ],
//     },
//   ],
// };
 
// rest data
const initializeRestData = (quarters) => {
  return Object.keys(TaxEntitiesKeys).map((taxKey) => {
    const taxKeyTyped = taxKey;
 
    return {
      title: taxKeyTyped,
      periods: Object.entries(quarters).map(([_, data]) => ({
        months: data.months.map((month) => month[taxKeyTyped]),
        total: data.total ? data.total[taxKeyTyped] : null,
      })),
    };
  });
};
 
console.log(
  JSON.stringify({ restData: initializeRestData(groupedQuarters) }, null, 2),
);
// rest data
 
// {
//   restData: [
//     {
//       title: "profit",
//       periods: [
//         {
//           months: [300, 500],
//           total: 2000,
//         },
//         {
//           months: [700],
//           total: 3000,
//         },
//       ],
//     },
//     {
//       title: "profit_percent",
//       periods: [
//         {
//           months: [30, 50],
//           total: 200,
//         },
//         {
//           months: [70],
//           total: 250,
//         },
//       ],
//     },
//   ],
// };

innerJoin

Implements a basic innerJoin function for arrays — similar to SQL inner joins. This method takes a predicate, a list of records, and a list of ids. It returns all records where the predicate returns true for at least one ID.

This approach is helpful for:

The example joins musician records by matching their id with an array of selected IDs.

function innerJoin(predicate, records, ids) {
  return records.filter((record) => ids.some((id) => predicate(record, id)));
}
 
const result = innerJoin(
  (record, id) => record.id === id,
  [
    { id: 824, name: "Richie Furay" },
    { id: 956, name: "Dewey Martin" },
    { id: 313, name: "Bruce Palmer" },
    { id: 456, name: "Stephen Stills" },
    { id: 177, name: "Neil Young" },
  ],
  [177, 456, 999],
);
 
console.log(result);
// [{id: 456, name: 'Stephen Stills'}, {id: 177, name: 'Neil Young'}]

list to tree

This utility transforms a flat list of items (with id and parentId fields) into a nested tree structure. It uses a Map to efficiently link children to their parents while preserving the hierarchy.

It also includes a treeToList() function that flattens a tree back into a list — useful for data normalization, storing tree nodes in databases, or syncing front-end trees with backend updates.

function listToTree(items) {
  const map = new Map();
  const roots = [];
 
  // Map items by ID
  for (const item of items) {
    map.set(item.id, { ...item, children: [] });
  }
 
  for (const item of items) {
    const node = map.get(item.id);
 
    if (item.parentId === null) {
      roots.push(node);
    } else {
      const parent = map.get(item.parentId);
 
      if (parent) {
        parent.children.push(node);
      }
    }
  }
 
  return roots;
}
 
const items = [
  { id: 1, name: "Root 1", parentId: null },
  { id: 2, name: "Child 1.1", parentId: 1 },
  { id: 3, name: "Child 1.2", parentId: 1 },
  { id: 4, name: "Root 2", parentId: null },
  { id: 5, name: "Child 2.1", parentId: 4 },
  { id: 6, name: "SubChild 2.1.1", parentId: 5 },
];
 
const tree = listToTree(items);
console.log(tree);
// [
//   {
//     "id": 1,
//     "name": "Root 1",
//     "parentId": null,
//     "children": [
//       {
//         "id": 2,
//         "name": "Child 1.1",
//         "parentId": 1,
//         "children": []
//       },
//       {
//         "id": 3,
//         "name": "Child 1.2",
//         "parentId": 1,
//         "children": []
//       }
//     ]
//   },
//   {
//     "id": 4,
//     "name": "Root 2",
//     "parentId": null,
//     "children": [
//       {
//         "id": 5,
//         "name": "Child 2.1",
//         "parentId": 4,
//         "children": [
//           {
//             "id": 6,
//             "name": "SubChild 2.1.1",
//             "parentId": 5,
//             "children": []
//           }
//         ]
//       }
//     ]
//   }
// ]
 
function treeToList(tree) {
  const list = [];
 
  function traverse(node) {
    const { children, ...rest } = node;
    list.push(rest);
 
    if (children) {
      for (const child of children) {
        traverse(child);
      }
    }
  }
 
  for (const root of tree) {
    traverse(root);
  }
 
  return list;
}
 
const flatList = treeToList(tree);
console.log(flatList);
// [
//   { id: 1, name: 'Root 1', parentId: null },
//   { id: 2, name: 'Child 1.1', parentId: 1 },
//   { id: 3, name: 'Child 1.2', parentId: 1 },
//   { id: 4, name: 'Root 2', parentId: null },
//   { id: 5, name: 'Child 2.1', parentId: 4 },
//   { id: 6, name: 'SubChild 2.1.1', parentId: 5 }
// ]

promises, closures and event loop mix

This section demonstrates how JavaScript promises, closures, and the event loop interact in real-world scenarios. These examples cover:

Perfect for understanding async behavior, state retention, and timing execution in complex JavaScript code.

Example 1: Async function with closure and delayed execution

This example shows how closures capture state (count) and how await affects the execution order. Each call to asyncCounter() increments count and logs it immediately. However, await delays further code inside the async function, allowing subsequent calls to update count before the first finishes.

function createAsyncCounter() {
  let count = 0;
  return async function incrementAsyncCounter() {
    count++;
    console.log({ count });
    await new Promise((resolve) => setTimeout(resolve, 1000));
    console.log("Async Counter:", count);
  };
}
 
const asyncCounter = createAsyncCounter();
 
asyncCounter();
asyncCounter().then(() => {
  asyncCounter();
});
 
console.log("Script complete");
 
// { count: 1 }
// { count: 2 }
// Script complete
// 1 s delay
// Async Counter: 2
// Async Counter: 2
// { count: 3 }
// 1 s delay
// Async Counter: 3

Example 2: Sequential async calls with await and closure

This example demonstrates how sequential await calls work with closures. Each async call completes before the next one begins, ensuring the counter increments correctly. The count variable is preserved across invocations due to the closure, and each setTimeout resolves after 1 second, leading to predictable, step-by-step output.

function createAsyncCounter() {
  let count = 0;
  return async function incrementAsyncCounter() {
    count++;
    await new Promise((resolve) => setTimeout(resolve, 1000));
    console.log("Async Counter:", count);
  };
}
 
const asyncCounter = createAsyncCounter();
 
await asyncCounter();
await asyncCounter();
await asyncCounter();
 
console.log("Script complete");
 
// with 1s between:
// Async Counter: 1
// Async Counter: 2
// Async Counter: 3
// immediately after counter 3:
// Script complete

Example 3: Task scheduling with closures and async timing

This example highlights how closure captures the shared taskCount variable, which is incremented before each scheduled task. All tasks reference the same taskCount value by the time their setTimeout callbacks run, resulting in each log displaying Task Count: 3. It also demonstrates how microtasks (like Promise.resolve().then) run before timers and the event loop order between synchronous code, promises, and setTimeout.

function createTaskScheduler() {
  let taskCount = 0;
  return function scheduleTask() {
    taskCount++;
    setTimeout(() => {
      console.log("Task Count:", taskCount);
    }, taskCount * 1000);
  };
}
 
const scheduleTask = createTaskScheduler();
 
scheduleTask();
scheduleTask();
 
Promise.resolve().then(() => {
  console.log("promise");
  scheduleTask();
});
 
console.log("Tasks scheduled");
 
// Tasks scheduled
// promise
// after 1s with 1s between
// Task Count: 3
// Task Count: 3
// Task Count: 3

Example 4: Scheduling tasks with preserved state using closures

In this version, the closure captures the taskCount value in a separate variable savedCount before scheduling the setTimeout. This ensures that each task logs the correct value at the time it was created, rather than referencing the final shared taskCount. The result is that each setTimeout logs a different value in increasing order, showcasing how to avoid timing issues by saving state early.

function createTaskScheduler() {
  let taskCount = 0;
  return function scheduleTask() {
    taskCount++;
    const savedCount = taskCount;
    setTimeout(() => {
      console.log("Task Count:", savedCount);
    }, taskCount * 1000);
  };
}
 
const scheduleTask = createTaskScheduler();
 
scheduleTask();
scheduleTask();
 
Promise.resolve().then(() => {
  scheduleTask();
});
 
console.log("Tasks scheduled");
 
// Tasks scheduled
// after 1s with 1s between:
// Task Count: 1
// Task Count: 2
// Task Count: 3

Example 5: Preserving async state with closure in chained promises

This example demonstrates how closures can maintain and update state (count) across multiple asynchronous calls. Because the promise is chained (then -> then), each counter() call executes sequentially, allowing count to increment between calls. The use of setTimeout inside the promise simulates asynchronous work, and the closure ensures the count value is correctly preserved across executions.

function createCounter() {
  let count = 0;
  return function incrementCounter() {
    count++;
    return new Promise((resolve) => {
      setTimeout(() => {
        resolve(count);
      }, 1000);
    });
  };
}
 
const counter = createCounter();
 
// if it was called at the same time, count would
// have not been saved in closure
 
counter()
  .then((result) => {
    console.log("Counter 1:", result);
    return counter();
  })
  .then((result) => {
    console.log("Counter 2:", result);
  });
 
console.log("Script in progress");
 
// Script in progress
// after 1s with 1s between:
// Counter 1: 1
// Counter 2: 2

Example 6: Asynchronous multiplier using closure and chained promises

This example showcases how closures can maintain internal state (factor = 2) in an asynchronous function. The multiplyByTwo function, returned by createAsyncMultiplier, remembers the factor across multiple chained .then() calls. Each multiplication is delayed with setTimeout, and the result of one multiplication is passed to the next, demonstrating sequential asynchronous logic with shared state.

function createAsyncMultiplier() {
  let factor = 2;
 
  return function multiply(value) {
    return new Promise((resolve) => {
      setTimeout(() => {
        resolve(value * factor);
      }, 1000);
    });
  };
}
 
const multiplyByTwo = createAsyncMultiplier();
 
multiplyByTwo(5)
  .then((result) => {
    console.log("Multiply 1:", result);
    return multiplyByTwo(result);
  })
  .then((result) => {
    console.log("Multiply 2:", result);
    return multiplyByTwo(result);
  })
  .then((result) => {
    console.log("Multiply 3:", result);
  });
 
console.log("Multiplication started");
 
// Multiplication started
// after 1s with 1s between:
// Multiply 1: 10
// Multiply 2: 20
// Multiply 3: 40

Reducer pattern with actions

This example demonstrates the use of a reducer function similar to the pattern used in React’s useReducer. The reducer function handles different types of actions (added, changed, deleted) to manage a list of tasks. It accumulates state changes over time as actions are applied via Array.prototype.reduce, making it a powerful pattern for predictable state updates in complex logic flows.

function tasksReducer(tasks, action) {
  switch (action.type) {
    case "added": {
      return [
        ...tasks,
        {
          id: action.id,
          text: action.text,
          done: false,
        },
      ];
    }
    case "changed": {
      return tasks.map((t) => {
        if (t.id === action.id) {
          const { type, ...actionNoType } = action;
          return actionNoType;
        } else {
          return t;
        }
      });
    }
    case "deleted": {
      return tasks.filter((t) => t.id !== action.id);
    }
    default: {
      throw Error("Unknown action: " + action.type);
    }
  }
}
 
const initialState = [];
const actions = [
  { type: "added", id: 1, text: "Visit Kafka Museum" },
  { type: "added", id: 2, text: "Watch a puppet show" },
  { type: "deleted", id: 1 },
  { type: "added", id: 3, text: "Lennon Wall pic" },
  { type: "changed", id: 3, text: "Lennon Wall", done: true },
];
const finalState = actions.reduce(tasksReducer, initialState);
console.log(finalState);
// [
//     { id: 2, text: 'Watch a puppet show', done: false },
//     { id: 3, text: 'Lennon Wall', done: true }
// ]

Retry with exponential backoff

This snippet demonstrates how to implement a retry mechanism when fetching data from an API. It attempts to fetch the resource multiple times (up to a given retries limit) and waits a specified delay between each retry. If the request ultimately fails, it throws an error. This is particularly useful for handling flaky network requests or ensuring robustness when dealing with unstable APIs.

async function fetchWithRetry(url, retries, delay = 1000) {
  for (let attempt = 1; attempt <= retries; attempt++) {
    try {
      const response = await fetch(url);
      if (!response.ok) {
        throw new Error(`HTTP Error: ${response.status}`);
      }
      return await response.json();
    } catch (error) {
      console.error(`Attempt ${attempt} failed:`, error);
 
      if (attempt === retries) {
        throw new Error(`Failed to fetch after ${retries} retries`);
      }
 
      await new Promise((resolve) => setTimeout(resolve, delay));
    }
  }
}
 
const res = await fetchWithRetry("https://pokeapi.co/api/v2/pokemon-color", 3);
console.log(res);

Shuffle (Fisher-Yates Algorithm)

This example implements the Fisher-Yates shuffle, a reliable way to randomly shuffle elements in an array. The algorithm works by iterating the array from the end to the beginning, swapping the current element with a randomly selected one from earlier in the array (or itself). It ensures a uniform distribution of permutations.

function shuffle(array) {
  for (let i = array.length - 1; i > 0; i--) {
    const j = Math.floor(Math.random() * (i + 1));
 
    [array[i], array[j]] = [array[j], array[i]];
  }
}
 
const shuffledArray = [1, 2, 3, 4, 5];
shuffle(shuffledArray);
console.log(shuffledArray); // [ 2, 1, 4, 5, 3 ]

Topological Sort

This example performs a topological sort on a set of items with dependencies. Each item can only appear in the result after all of its dependencies are resolved. It’s a common algorithm used in build systems, task schedulers, and dependency resolution tools.

const cards = [
  { id: 1, dependent: [6, 7, 8] },
  { id: 2, dependent: [6] },
  { id: 3, dependent: [] },
  { id: 4, dependent: [6, 7, 8] },
  { id: 5, dependent: [6, 8] },
  { id: 6, dependent: [] },
  { id: 7, dependent: [6] },
  { id: 8, dependent: [7] },
  { id: 9, dependent: [1] },
  { id: 10, dependent: [9] },
];
 
const getOrderedCards = (cards) => {
  const result = [];
  const added = new Set();
 
  while (result.length < cards.length) {
    for (const card of cards) {
      if (
        !added.has(card.id) &&
        card.dependent.every((dep) => added.has(dep))
      ) {
        result.push(card.id);
        added.add(card.id);
      }
    }
  }
 
  return result;
};
 
console.log(getOrderedCards(cards));
// [
//   3, 6, 7, 8,  1,
//   2, 4, 5, 9, 10
// ]