├── README.md ├── data-structures ├── queue │ ├── index.js │ ├── package.json │ └── queue.js └── stack │ ├── index.js │ ├── package.json │ └── stack.js ├── design-patterns ├── README.md ├── command-pattern │ ├── AddCommand.js │ ├── Calculator.js │ ├── SubtractCommand.js │ ├── index.js │ └── package.json ├── factory-method-pattern │ ├── Factory.js │ ├── index.js │ └── package.json ├── module-pattern │ ├── browser │ │ ├── es │ │ │ ├── script1-es.js │ │ │ └── script2-es.js │ │ ├── index.html │ │ ├── script1.js │ │ └── script2.js │ └── node │ │ ├── index.js │ │ ├── package.json │ │ ├── script-es.mjs │ │ └── script.js ├── observer-pattern │ ├── OObservable.js │ ├── Observable.js │ ├── index.js │ └── package.json ├── proxy-pattern │ ├── index.js │ └── package.json ├── pub-sub-pattern │ ├── Broker.js │ ├── Publisher.js │ ├── index.js │ └── package.json ├── singleton-pattern │ ├── DBConnection.js │ ├── ODBConnection.js │ ├── UpdatedDBConnection.js │ ├── index.js │ └── package.json ├── strategy-pattern │ ├── Strategy1.js │ ├── Strategy2.js │ ├── StrategyManager.js │ ├── index.js │ └── package.json └── visitor-pattern │ ├── README.md │ ├── index.mjs │ ├── package.json │ └── src │ ├── BinaryExpression.mjs │ ├── NumericLiteral.mjs │ └── Visitor.mjs ├── dsa-questions └── leetcode │ ├── best-time-to-buy-and-sell-stock-ii │ └── best-time-to-buy-and-sell-stock-ii.js │ ├── climbing-stairs │ └── climbing-stairs.js │ ├── combination-sum │ └── combination-sum.js │ ├── contains-duplicate │ └── contains-duplicate.js │ ├── delete-node-in-a-linked-list │ └── delete-node-in-a-linked-list.js │ ├── first-unique-character-in-a-string │ └── first-unique-character-in-a-string.js │ ├── linked-list-cycle │ └── linked-list-cycle.js │ ├── matrix-diagonal-sum │ └── matrix-diagonal-sum.js │ ├── maximum-depth-of-binary-tree │ └── maximum-depth-of-binary-tree.js │ ├── merge-two-sorted-lists │ └── merge-two-sorted-lists.js │ ├── move-zeroes │ └── move-zeroes.js │ ├── palindrome-linked-list │ └── palindrome-linked-list.js │ ├── plus-one │ └── plus-one.js │ ├── remove-duplicates-from-sorted-array │ └── remove-duplicates-from-sorted-array.js │ ├── remove-nth-node-from-end-of-list │ └── remove-nth-node-from-end-of-list.js │ ├── reverse-linked-list │ └── reverse-linked-list.js │ ├── reverse-string │ └── reverse-string.js │ ├── rotate-array │ └── rotate-array.js │ ├── rotate-image │ └── rotate-image.js │ ├── single-number │ └── single-number.js │ ├── subsets │ └── subsets.js │ ├── symmetric-tree │ └── symmetric-tree.js │ ├── transpose-matrix │ └── transpose-matrix.js │ ├── two-sum │ └── two-sum.js │ ├── valid-anagram │ └── valid-anagram.js │ ├── valid-palindrome │ └── valid-palindrome.js │ └── validate-binary-search-tree │ └── validate-binary-search-tree.js ├── frontend-machine-coding ├── README.md ├── accordion │ ├── index.html │ ├── scripts │ │ ├── index.js │ │ └── lib.js │ └── style.css ├── bishop-chessboard │ ├── index.html │ ├── scripts │ │ ├── index.js │ │ └── lib.js │ └── style.css ├── carousel │ ├── index.html │ ├── scripts │ │ ├── index.js │ │ └── lib.js │ └── style.css ├── index.html ├── kanban-board │ ├── components │ │ ├── Column │ │ │ ├── Column.js │ │ │ └── column.styles.css │ │ ├── Kanban │ │ │ ├── Kanban.js │ │ │ └── kanban.styles.css │ │ ├── Overlay │ │ │ ├── Overlay.js │ │ │ └── overlay.styles.css │ │ └── Task │ │ │ ├── Task.js │ │ │ └── task.styles.css │ ├── index.html │ ├── index.js │ ├── services │ │ ├── API.js │ │ └── LocalStorageAPI.js │ ├── style.css │ └── utils.js ├── modal-component │ ├── index.html │ ├── scripts │ │ ├── index.js │ │ └── lib.js │ └── style.css ├── otp-input │ ├── index.html │ ├── scripts │ │ ├── index.js │ │ └── lib.js │ └── style.css ├── star-rating │ ├── index.html │ ├── scripts │ │ ├── index.js │ │ └── lib.js │ └── style.css ├── style.css ├── tabs │ ├── index.html │ ├── scripts │ │ ├── index.js │ │ └── lib.js │ └── style.css ├── tic-tac-toe │ ├── index.html │ ├── scripts │ │ ├── index.js │ │ └── lib.js │ └── style.css └── todo-list │ ├── components │ ├── AddItem │ │ ├── AddItem.js │ │ └── add_item.style.css │ ├── Item │ │ ├── Item.js │ │ └── item.style.css │ ├── ItemsContainer │ │ ├── ItemsContainer.js │ │ └── items_container.style.css │ └── Todo │ │ └── Todo.js │ ├── index.html │ ├── index.js │ ├── services │ ├── API.js │ ├── IndexedDbAPI.js │ └── LocalStorageAPI.js │ ├── style.css │ └── utils.js ├── important-concepts ├── EventEmitter.js ├── auto-retry-fetch.js ├── brute-force-currying.js ├── classNames.js ├── currying.js ├── debounce.js ├── flatten-1.js ├── flatten-2.js ├── immer.js ├── isEqual.js ├── memoize.js ├── memoizeLast.js ├── negative-indexing.js ├── pipe.js ├── throttling.js ├── typeof.js ├── virtual-dom-deserialization-II │ ├── index.html │ ├── index.js │ └── virtualTree.js └── virtual-dom-serialization-I │ ├── index.html │ └── index.js └── polyfills ├── JSON └── JSON.stringify.js ├── _ └── _.once.js ├── array ├── at.js ├── concat.js ├── entries.js ├── every.js ├── fill.js ├── filter.js ├── find.js ├── findIndex.js ├── findLast.js ├── findLastIndex.js ├── flat.js ├── forEach.js ├── includes.js ├── indexOf.js ├── join.js ├── keys.js ├── lastIndexOf.js ├── map.js ├── pop.js ├── push.js ├── reduce.js ├── reverse.js ├── shift.js ├── slice.js ├── some.js ├── unshift.js └── values.js ├── functions ├── apply.js ├── bind.js └── call.js ├── object ├── Object.assign.js └── Object.is.js ├── promises ├── promise.all.js ├── promise.allSettled.js └── promise.any.js ├── string └── trim.js └── window ├── clearAllTimers.js ├── setInterval.js └── setTimeout.js /README.md: -------------------------------------------------------------------------------- 1 | # JavaScript Interview Preparation 2 | 3 | I know it is quite hectic to prepare for interviews. Specially, JavaScript ones where the grilling is increasing. Hence, my motivation for creating this repository was to have a single place to cover all of my interview preparation needs. This repository' focus will be towards JavaScript but I am maintaining another repository for [React.js](https://github.com/mohitkumartoshniwal/reactjs-interview-preparation/tree/main) 4 | 5 | ## Frontend Machine Coding Interview Questions in Javascript 6 | 7 | | Problem | Video | Solution | 8 | | --------------------- | ------------------------------------- | ------------------------------------------------------- | 9 | | Star Rating Component | [Video](https://youtu.be/QsvPgVV_1Os) | [Solution](./frontend-machine-coding/star-rating) | 10 | | Modal Component | [Video](https://youtu.be/E6z42PHIuLA) | [Solution](./frontend-machine-coding/modal-component) | 11 | | Carousel Component | [Video](https://youtu.be/qX5qjRnHSnc) | [Solution](./frontend-machine-coding/carousel) | 12 | | Accordion Component | [Video](https://youtu.be/iQpX0W18Yb0) | [Solution](./frontend-machine-coding/accordion) | 13 | | OTP Input Component | [Video](https://youtu.be/dAvy4OYZpHk) | [Solution](./frontend-machine-coding/otp-input) | 14 | | Bishop on Chessboard | [Video](https://youtu.be/aY2ra2BDBd8) | [Solution](./frontend-machine-coding/bishop-chessboard) | 15 | | Tabs Component | [Video](https://youtu.be/Q-oCO7so_lc) | [Solution](./frontend-machine-coding/tabs) | 16 | | Tic Tac Toe | [Video](https://youtu.be/5u-ENEknLXs) | [Solution](./frontend-machine-coding/tic-tac-toe) | 17 | | Todo List | [Video]() | [Solution](./frontend-machine-coding/todo-list/) | 18 | 19 | You can check out the above solutions live [here](https://frontend-machine-coding.vercel.app/) 20 | 21 | ## Design Patterns in Javascript 22 | 23 | | Pattern | Video | Solution | 24 | | ---------------------------- | ---------------------------------------------------- | ----------------------------------------------------- | 25 | | Module Pattern | [Video](https://www.youtube.com/watch?v=4mH3-EfjiPQ) | [Solution](./design-patterns/module-pattern/) | 26 | | Singleton Pattern | [Video](https://www.youtube.com/watch?v=IhCc1oPMomE) | [Solution](./design-patterns/singleton-pattern/) | 27 | | Observer Pattern | [Video](https://www.youtube.com/watch?v=asR1lpB0WTE) | [Solution](./design-patterns/observer-pattern/) | 28 | | Publisher Subscriber Pattern | [Video](https://www.youtube.com/watch?v=kRqKkNbX8ug) | [Solution](./design-patterns/pub-sub-pattern/) | 29 | | Proxy Pattern | [Video](https://www.youtube.com/watch?v=cbCQzjEFqKk) | [Solution](./design-patterns/proxy-pattern/) | 30 | | Command Pattern | [Video](https://www.youtube.com/watch?v=XJ2msSGeWP8) | [Solution](./design-patterns/command-pattern/) | 31 | | Factory Method Pattern | [Video](https://www.youtube.com/watch?v=8j3YXtAzacM) | [Solution](./design-patterns/factory-method-pattern/) | 32 | | Strategy Pattern | [Video](https://www.youtube.com/watch?v=Q2-ZpDrSc5E) | [Solution](./design-patterns/strategy-pattern/) | 33 | | Visitor Pattern | [Video](https://www.youtube.com/watch?v=7XCYhP0t9a8) | [Solution](./design-patterns/visitor-pattern/) | 34 | 35 | ## ⭐️ Show Your Support 36 | 37 | Please give a ⭐️ if this project helped you! 38 | -------------------------------------------------------------------------------- /data-structures/queue/index.js: -------------------------------------------------------------------------------- 1 | import { Queue } from "./queue.js"; 2 | 3 | const queue = new Queue(); 4 | queue.enqueue(2); 5 | queue.enqueue(3); 6 | queue.enqueue(7); 7 | console.log(`Elements in Queue: ${queue.items}`); 8 | console.log(`Removed element from Queue: ${queue.dequeue()}`); 9 | console.log(`Is Queue Empty?: ${queue.isEmpty()}`); 10 | console.log(`Front element in queue: ${queue.front()}`); 11 | console.log(`Rear element in queue: ${queue.rear()}`); 12 | console.log(`Size of Queue: ${queue.size()}`); 13 | 14 | /* 15 | Output: 16 | 17 | Elements in Queue: 2,3,7 18 | Removed element from Queue: 2 19 | Is Queue Empty?: false 20 | Front element in queue: 3 21 | Rear element in queue: 7 22 | Size of Queue: 2 23 | */ 24 | -------------------------------------------------------------------------------- /data-structures/queue/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "queue", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "type": "module", 7 | "scripts": { 8 | "test": "echo \"Error: no test specified\" && exit 1" 9 | }, 10 | "keywords": [], 11 | "author": "", 12 | "license": "ISC" 13 | } 14 | -------------------------------------------------------------------------------- /data-structures/queue/queue.js: -------------------------------------------------------------------------------- 1 | export class Queue { 2 | constructor() { 3 | this.items = []; 4 | } 5 | 6 | // Add new element in queue 7 | enqueue(item) { 8 | this.items.push(item); 9 | } 10 | 11 | // Remove element from queue 12 | dequeue() { 13 | return this.items.shift(); 14 | } 15 | 16 | // Return first element from queue 17 | front() { 18 | return this.items[0]; 19 | } 20 | 21 | // Return last element from queue 22 | rear() { 23 | return this.items[this.items.length - 1]; 24 | } 25 | 26 | // Check if Queue is empty 27 | isEmpty() { 28 | return this.items.length === 0; 29 | } 30 | 31 | // Return size of Queue 32 | size() { 33 | return this.items.length; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /data-structures/stack/index.js: -------------------------------------------------------------------------------- 1 | import { Stack } from "./stack.js"; 2 | 3 | const stack = new Stack(); 4 | stack.push(2); 5 | stack.push(3); 6 | console.log(`Elements in Stack: ${stack.items}`); 7 | console.log(`Popped element from Stack: ${stack.pop()}`); 8 | console.log(`Is Stack Empty?: ${stack.isEmpty()}`); 9 | console.log(`Top element in Stack: ${stack.peek()}`); 10 | console.log(`Size of Stack: ${stack.size()}`); 11 | stack.clear(); 12 | console.log(`Size of Stack: ${stack.size()}`); 13 | 14 | /* 15 | Output: 16 | 17 | Elements in Stack: 2,3 18 | Popped element from Stack: 3 19 | Is Stack Empty?: false 20 | Top element in Stack: 2 21 | Size of Stack: 1 22 | Size of Stack: 0 23 | */ 24 | -------------------------------------------------------------------------------- /data-structures/stack/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "stack", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "type": "module", 7 | "scripts": { 8 | "test": "echo \"Error: no test specified\" && exit 1" 9 | }, 10 | "keywords": [], 11 | "author": "", 12 | "license": "ISC" 13 | } 14 | -------------------------------------------------------------------------------- /data-structures/stack/stack.js: -------------------------------------------------------------------------------- 1 | export class Stack { 2 | constructor() { 3 | this.items = []; 4 | } 5 | 6 | // Push an item in the Stack 7 | push(item) { 8 | this.items.push(item); 9 | } 10 | 11 | // Pop an item from the Stack 12 | pop() { 13 | return this.items.pop(); 14 | } 15 | 16 | // Peek top item from the Stack 17 | peek() { 18 | return this.items[this.items.length - 1]; 19 | } 20 | 21 | // is Stack Empty? 22 | isEmpty() { 23 | return this.items.length === 0; 24 | } 25 | 26 | // Clear the Stack 27 | clear() { 28 | this.items.length = 0; 29 | } 30 | 31 | // Size of the Stack 32 | size() { 33 | return this.items.length; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /design-patterns/README.md: -------------------------------------------------------------------------------- 1 | # Design Patterns in Javascript 2 | 3 | | Pattern | Video | Solution | 4 | | ---------------------------- | ---------------------------------------------------- | ------------------------------------- | 5 | | Module Pattern | [Video](https://www.youtube.com/watch?v=4mH3-EfjiPQ) | [Solution](./module-pattern/) | 6 | | Singleton Pattern | [Video](https://www.youtube.com/watch?v=IhCc1oPMomE) | [Solution](./singleton-pattern/) | 7 | | Observer Pattern | [Video](https://www.youtube.com/watch?v=asR1lpB0WTE) | [Solution](./observer-pattern/) | 8 | | Publisher Subscriber Pattern | [Video](https://www.youtube.com/watch?v=kRqKkNbX8ug) | [Solution](./pub-sub-pattern/) | 9 | | Proxy Pattern | [Video](https://www.youtube.com/watch?v=cbCQzjEFqKk) | [Solution](./proxy-pattern/) | 10 | | Command Pattern | [Video](https://www.youtube.com/watch?v=XJ2msSGeWP8) | [Solution](./command-pattern/) | 11 | | Factory Method Pattern | [Video](https://www.youtube.com/watch?v=8j3YXtAzacM) | [Solution](./factory-method-pattern/) | 12 | | Strategy Pattern | [Video](https://www.youtube.com/watch?v=Q2-ZpDrSc5E) | [Solution](./strategy-pattern/) | 13 | | Visitor Pattern | [Video](https://www.youtube.com/watch?v=7XCYhP0t9a8) | [Solution](./visitor-pattern/) | 14 | -------------------------------------------------------------------------------- /design-patterns/command-pattern/AddCommand.js: -------------------------------------------------------------------------------- 1 | export class AddCommand { 2 | constructor(amount) { 3 | this.amount = amount 4 | } 5 | 6 | execute(value) { 7 | return value + this.amount 8 | } 9 | 10 | undo(value) { 11 | return value - this.amount 12 | } 13 | } -------------------------------------------------------------------------------- /design-patterns/command-pattern/Calculator.js: -------------------------------------------------------------------------------- 1 | export class Calculator { 2 | constructor() { 3 | this.currentValue = 0 4 | this.commands = [] 5 | } 6 | 7 | execute(command) { 8 | this.currentValue = command.execute(this.currentValue) 9 | this.commands.push(command) 10 | } 11 | 12 | undo() { 13 | let command = this.commands.pop() 14 | this.currentValue = command.undo(this.currentValue) 15 | } 16 | } -------------------------------------------------------------------------------- /design-patterns/command-pattern/SubtractCommand.js: -------------------------------------------------------------------------------- 1 | export class SubtractCommand { 2 | constructor(amount) { 3 | this.amount = amount 4 | } 5 | 6 | execute(value) { 7 | return value - this.amount 8 | } 9 | 10 | undo(value) { 11 | return value + this.amount 12 | } 13 | } -------------------------------------------------------------------------------- /design-patterns/command-pattern/index.js: -------------------------------------------------------------------------------- 1 | import { AddCommand } from "./AddCommand.js"; 2 | import { Calculator } from "./Calculator.js"; 3 | import { SubtractCommand } from "./SubtractCommand.js"; 4 | 5 | 6 | let calculator = new Calculator() 7 | 8 | calculator.execute(new AddCommand(3)) 9 | console.log(calculator.currentValue) 10 | 11 | calculator.execute(new AddCommand(5)) 12 | console.log(calculator.currentValue) 13 | 14 | calculator.execute(new SubtractCommand(1)) 15 | console.log(calculator.currentValue) 16 | 17 | calculator.undo() 18 | console.log(calculator.currentValue) 19 | 20 | calculator.undo() 21 | console.log(calculator.currentValue) 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /design-patterns/command-pattern/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "command-pattern", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "type": "module", 7 | "scripts": { 8 | "test": "echo \"Error: no test specified\" && exit 1" 9 | }, 10 | "keywords": [], 11 | "author": "", 12 | "license": "ISC" 13 | } 14 | -------------------------------------------------------------------------------- /design-patterns/factory-method-pattern/Factory.js: -------------------------------------------------------------------------------- 1 | export function createUser({ name, email, role }) { 2 | let user = { 3 | name, 4 | email 5 | } 6 | 7 | if (role === "ADMIN") { 8 | user.role = 'ADMIN' 9 | } else if (role = "NORMAL") { 10 | user.role = "NORMAL" 11 | } 12 | 13 | return user 14 | } -------------------------------------------------------------------------------- /design-patterns/factory-method-pattern/index.js: -------------------------------------------------------------------------------- 1 | import { createUser } from "./Factory.js"; 2 | 3 | let user1 = createUser({ name: 'Mohit', email: 'm@g.com', role: 'NORMAL' }) 4 | console.log({ user1 }) 5 | 6 | let user2 = createUser({ name: 'John', email: 'j@g.com', role: 'ADMIN' }) 7 | console.log({ user2 }) -------------------------------------------------------------------------------- /design-patterns/factory-method-pattern/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "factory-method-pattern", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "type": "module", 7 | "scripts": { 8 | "test": "echo \"Error: no test specified\" && exit 1" 9 | }, 10 | "keywords": [], 11 | "author": "", 12 | "license": "ISC" 13 | } 14 | -------------------------------------------------------------------------------- /design-patterns/module-pattern/browser/es/script1-es.js: -------------------------------------------------------------------------------- 1 | let a = 1; 2 | 3 | export function get() { 4 | return a 5 | } 6 | 7 | export function increment() { 8 | a++ 9 | } 10 | 11 | export default { get, increment } 12 | 13 | -------------------------------------------------------------------------------- /design-patterns/module-pattern/browser/es/script2-es.js: -------------------------------------------------------------------------------- 1 | // import { get, increment } from "./script1-es.js"; 2 | 3 | 4 | // console.log(get()) 5 | // console.log(increment()) 6 | // console.log(get()) 7 | 8 | import MODULE_NAMESPACE from './script1-es.js' 9 | 10 | console.log(MODULE_NAMESPACE.get()) 11 | console.log(MODULE_NAMESPACE.increment()) 12 | console.log(MODULE_NAMESPACE.get()) 13 | -------------------------------------------------------------------------------- /design-patterns/module-pattern/browser/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Module Pattern 7 | 8 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /design-patterns/module-pattern/browser/script1.js: -------------------------------------------------------------------------------- 1 | // let a = 1; 2 | 3 | let MODULE_NAMESPACE = (function iife() { 4 | let a = 1; 5 | 6 | function get() { 7 | return a 8 | } 9 | 10 | function increment() { 11 | a++ 12 | } 13 | 14 | return { 15 | get, 16 | increment 17 | } 18 | }()) -------------------------------------------------------------------------------- /design-patterns/module-pattern/browser/script2.js: -------------------------------------------------------------------------------- 1 | // console.log(a) 2 | 3 | console.log(MODULE_NAMESPACE.get()) 4 | console.log(MODULE_NAMESPACE.increment()) 5 | console.log(MODULE_NAMESPACE.get()) -------------------------------------------------------------------------------- /design-patterns/module-pattern/node/index.js: -------------------------------------------------------------------------------- 1 | // const { get, increment } = require("./script") 2 | 3 | 4 | // console.log(get()) 5 | // console.log(increment()) 6 | // console.log(get()) 7 | 8 | 9 | // const MODULE_NAMESPACE = require("./script") 10 | 11 | // console.log(MODULE_NAMESPACE.get()) 12 | // console.log(MODULE_NAMESPACE.increment()) 13 | // console.log(MODULE_NAMESPACE.get()) 14 | 15 | import { get, increment } from "./script-es.mjs" 16 | 17 | console.log(get()) 18 | console.log(increment()) 19 | console.log(get()) -------------------------------------------------------------------------------- /design-patterns/module-pattern/node/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "node", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "type": "module", 7 | "scripts": { 8 | "test": "echo \"Error: no test specified\" && exit 1" 9 | }, 10 | "keywords": [], 11 | "author": "", 12 | "license": "ISC" 13 | } 14 | -------------------------------------------------------------------------------- /design-patterns/module-pattern/node/script-es.mjs: -------------------------------------------------------------------------------- 1 | let a = 1; 2 | 3 | export function get() { 4 | return a 5 | } 6 | 7 | export function increment() { 8 | a++ 9 | } 10 | 11 | export default { get, increment } 12 | 13 | -------------------------------------------------------------------------------- /design-patterns/module-pattern/node/script.js: -------------------------------------------------------------------------------- 1 | let a = 1; 2 | 3 | function get() { 4 | return a 5 | } 6 | 7 | function increment() { 8 | a++ 9 | } 10 | 11 | module.exports = { 12 | get, 13 | increment 14 | } -------------------------------------------------------------------------------- /design-patterns/observer-pattern/OObservable.js: -------------------------------------------------------------------------------- 1 | let observers = [] 2 | 3 | let Observable = { 4 | addObserver: (observer) => { 5 | observers.push(observer) 6 | }, 7 | 8 | removeObserver: (observer) => { 9 | observers.forEach((ob, i) => { 10 | if (observer === ob) { 11 | observers.splice(i, 1) 12 | } 13 | }) 14 | }, 15 | 16 | notifyObservers: (data) => { 17 | observers.forEach((observer) => observer(data)) 18 | } 19 | } 20 | 21 | export default Observable -------------------------------------------------------------------------------- /design-patterns/observer-pattern/Observable.js: -------------------------------------------------------------------------------- 1 | export default class Observable { 2 | 3 | constructor() { 4 | this.observers = [] 5 | } 6 | 7 | addObserver(observer) { 8 | this.observers.push(observer) 9 | } 10 | 11 | removeObserver(observer) { 12 | this.observers.forEach((ob, i) => { 13 | if (observer === ob) { 14 | this.observers.splice(i, 1) 15 | } 16 | }) 17 | } 18 | 19 | notifyObservers(data) { 20 | this.observers.forEach((observer) => observer(data)) 21 | } 22 | } -------------------------------------------------------------------------------- /design-patterns/observer-pattern/index.js: -------------------------------------------------------------------------------- 1 | import Observable from "./Observable.js"; 2 | import OObservable from "./OObservable.js"; 3 | 4 | let observable = new Observable(); 5 | 6 | function observer1(data) { 7 | console.log(`Observer1: ${data}`) 8 | } 9 | 10 | function observer2(data) { 11 | console.log(`Observer2: ${data}`) 12 | } 13 | 14 | observable.addObserver(observer1) 15 | observable.addObserver(observer2) 16 | 17 | observable.notifyObservers('New Data') 18 | 19 | observable.removeObserver(observer1) 20 | 21 | observable.notifyObservers('Observer1 removed') 22 | 23 | 24 | console.log('\n') 25 | 26 | OObservable.addObserver(observer1) 27 | OObservable.addObserver(observer2) 28 | 29 | OObservable.notifyObservers('New Data') 30 | 31 | OObservable.removeObserver(observer1) 32 | 33 | OObservable.notifyObservers('Observer1 removed') 34 | -------------------------------------------------------------------------------- /design-patterns/observer-pattern/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "observer-pattern", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "type": "module", 7 | "scripts": { 8 | "test": "echo \"Error: no test specified\" && exit 1" 9 | }, 10 | "keywords": [], 11 | "author": "", 12 | "license": "ISC" 13 | } 14 | -------------------------------------------------------------------------------- /design-patterns/proxy-pattern/index.js: -------------------------------------------------------------------------------- 1 | let user = { 2 | name: 'Mohit', 3 | email: 'm@g.com' 4 | } 5 | 6 | let userProxy = new Proxy(user, { 7 | get(target, property) { 8 | // return target[property] 9 | return Reflect.get(target, property) 10 | }, 11 | set(target, property, newValue) { 12 | // target[property] = newValue 13 | // return true 14 | if (property === 'name' && newValue.length < 3) { 15 | throw new Error('A proper name should be provided') 16 | } 17 | return Reflect.set(target, property, newValue) 18 | } 19 | }) 20 | 21 | console.log(userProxy.name) 22 | userProxy.name = 'John' 23 | console.log(userProxy.name) 24 | // userProxy.name = 'Sa' 25 | userProxy.name = 'Sam' 26 | console.log(userProxy.name) 27 | 28 | -------------------------------------------------------------------------------- /design-patterns/proxy-pattern/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "proxy-pattern", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "type": "module", 7 | "scripts": { 8 | "test": "echo \"Error: no test specified\" && exit 1" 9 | }, 10 | "keywords": [], 11 | "author": "", 12 | "license": "ISC" 13 | } 14 | -------------------------------------------------------------------------------- /design-patterns/pub-sub-pattern/Broker.js: -------------------------------------------------------------------------------- 1 | export default class Broker { 2 | constructor() { 3 | this.subscribers = {} 4 | } 5 | 6 | subscribe(event, fn) { 7 | if (this.subscribers[event]) { 8 | this.subscribers[event] = [...this.subscribers[event], fn] 9 | } else { 10 | this.subscribers[event] = [fn] 11 | } 12 | } 13 | 14 | unsubscribe(event, fn) { 15 | if (this.subscribers[event]) { 16 | this.subscribers[event] = this.subscribers[event].filter((sub) => sub !== fn) 17 | } 18 | } 19 | 20 | publish(event, data) { 21 | this.subscribers[event].forEach(sub => sub(data)); 22 | } 23 | } -------------------------------------------------------------------------------- /design-patterns/pub-sub-pattern/Publisher.js: -------------------------------------------------------------------------------- 1 | export default class Publisher { 2 | constructor(broker) { 3 | this.broker = broker 4 | } 5 | 6 | publish(event, data) { 7 | this.broker.publish(event, data) 8 | } 9 | } -------------------------------------------------------------------------------- /design-patterns/pub-sub-pattern/index.js: -------------------------------------------------------------------------------- 1 | import Broker from "./Broker.js"; 2 | import Publisher from "./Publisher.js"; 3 | 4 | function subscriber1(data) { 5 | console.log('Subscriber1 ', data) 6 | } 7 | 8 | function subscriber2(data) { 9 | console.log('Subscriber2 ', data) 10 | } 11 | 12 | let broker = new Broker() 13 | let publisher = new Publisher(broker) 14 | 15 | broker.subscribe('EVENT1', subscriber1) 16 | broker.subscribe('EVENT1', subscriber2) 17 | 18 | publisher.publish('EVENT1', 'New Data') 19 | 20 | broker.unsubscribe('EVENT1', subscriber1) 21 | 22 | publisher.publish('EVENT1', 'Subscriber 1 removed') 23 | 24 | -------------------------------------------------------------------------------- /design-patterns/pub-sub-pattern/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pub-sub-pattern", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "type": "module", 7 | "scripts": { 8 | "test": "echo \"Error: no test specified\" && exit 1" 9 | }, 10 | "keywords": [], 11 | "author": "", 12 | "license": "ISC" 13 | } 14 | -------------------------------------------------------------------------------- /design-patterns/singleton-pattern/DBConnection.js: -------------------------------------------------------------------------------- 1 | let instance; 2 | 3 | class DBConnection { 4 | 5 | constructor(uri) { 6 | if (instance) { 7 | throw new Error('One instance is already present') 8 | } 9 | instance = this 10 | this.uri = uri 11 | } 12 | 13 | connect() { 14 | console.log(`${this.uri} connected`) 15 | } 16 | disconnect() { 17 | console.log(`${this.uri} disconnected`) 18 | } 19 | } 20 | 21 | let dbConnection = Object.freeze(new DBConnection('some uri')) 22 | // let dbConnection = new DBConnection('some uri') 23 | 24 | export default dbConnection -------------------------------------------------------------------------------- /design-patterns/singleton-pattern/ODBConnection.js: -------------------------------------------------------------------------------- 1 | let uri = 'some uri' 2 | 3 | let DBConnection = { 4 | connect: () => { 5 | console.log(`${uri} connected`) 6 | }, 7 | disconnect: () => { 8 | console.log(`${uri} disconnected`) 9 | } 10 | } 11 | 12 | let dbConnection = Object.freeze(DBConnection) 13 | 14 | export default dbConnection -------------------------------------------------------------------------------- /design-patterns/singleton-pattern/UpdatedDBConnection.js: -------------------------------------------------------------------------------- 1 | // For better encapsulation 2 | 3 | class DBConnection { 4 | constructor(uri) { 5 | this.uri = uri 6 | } 7 | 8 | connect() { 9 | console.log(`DB ${this.uri} has been connected`) 10 | } 11 | 12 | 13 | disconnect() { 14 | console.log(`DB ${this.uri} has been disconnected`) 15 | } 16 | 17 | static getInstance(uri) { 18 | if (!this.instance) { 19 | this.instance = new DBConnection(uri); 20 | } 21 | return this.instance; 22 | } 23 | } 24 | 25 | 26 | const singletonDBConnection = DBConnection.getInstance('some uri'); 27 | Object.freeze(singletonDBConnection); 28 | 29 | export default singletonDBConnection -------------------------------------------------------------------------------- /design-patterns/singleton-pattern/index.js: -------------------------------------------------------------------------------- 1 | import DBConnection from "./DBConnection.js"; 2 | import ODBConnection from "./ODBConnection.js"; 3 | import UpdatedDBConnection from "./UpdatedDBConnection.js"; 4 | 5 | // DBConnection.newkey = 'value' 6 | 7 | DBConnection.connect() 8 | DBConnection.disconnect() 9 | console.log(DBConnection) 10 | console.log(DBConnection.constructor) 11 | 12 | // new DBConnection.constructor('some other uri') 13 | 14 | console.log('\n') 15 | 16 | ODBConnection.connect() 17 | ODBConnection.disconnect() 18 | console.log(ODBConnection) 19 | // console.log(ODBConnection.constructor) 20 | 21 | console.log('\n') 22 | 23 | UpdatedDBConnection.connect() 24 | UpdatedDBConnection.disconnect() 25 | console.log(UpdatedDBConnection) 26 | 27 | -------------------------------------------------------------------------------- /design-patterns/singleton-pattern/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "singleton-pattern", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "type": "module", 7 | "scripts": { 8 | "test": "echo \"Error: no test specified\" && exit 1" 9 | }, 10 | "keywords": [], 11 | "author": "", 12 | "license": "ISC" 13 | } 14 | -------------------------------------------------------------------------------- /design-patterns/strategy-pattern/Strategy1.js: -------------------------------------------------------------------------------- 1 | export class Strategy1 { 2 | performAction() { 3 | console.log('Strategy 1') 4 | } 5 | } -------------------------------------------------------------------------------- /design-patterns/strategy-pattern/Strategy2.js: -------------------------------------------------------------------------------- 1 | export class Strategy2 { 2 | performAction() { 3 | console.log('Strategy 2') 4 | } 5 | } -------------------------------------------------------------------------------- /design-patterns/strategy-pattern/StrategyManager.js: -------------------------------------------------------------------------------- 1 | export class StrategyManager { 2 | setStrategy(strategy) { 3 | this.strategy = strategy 4 | } 5 | 6 | performAction() { 7 | this.strategy.performAction() 8 | } 9 | } -------------------------------------------------------------------------------- /design-patterns/strategy-pattern/index.js: -------------------------------------------------------------------------------- 1 | import { Strategy1 } from "./Strategy1.js"; 2 | import { Strategy2 } from "./Strategy2.js"; 3 | import { StrategyManager } from "./StrategyManager.js"; 4 | 5 | let strategyManager = new StrategyManager() 6 | 7 | strategyManager.setStrategy(new Strategy1()) 8 | strategyManager.performAction() 9 | 10 | 11 | strategyManager.setStrategy(new Strategy2()) 12 | strategyManager.performAction() -------------------------------------------------------------------------------- /design-patterns/strategy-pattern/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "strategy-pattern", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "type": "module", 7 | "scripts": { 8 | "test": "echo \"Error: no test specified\" && exit 1" 9 | }, 10 | "keywords": [], 11 | "author": "", 12 | "license": "ISC" 13 | } 14 | -------------------------------------------------------------------------------- /design-patterns/visitor-pattern/README.md: -------------------------------------------------------------------------------- 1 | # Visitor Pattern 2 | 3 | Code for the [Youtube video](https://www.youtube.com/watch?v=7XCYhP0t9a8) on Visitor pattern in the context of AST 4 | 5 | -------------------------------------------------------------------------------- /design-patterns/visitor-pattern/index.mjs: -------------------------------------------------------------------------------- 1 | // https://blog.bitsrc.io/what-is-an-abstract-syntax-tree-7502b71bde27 2 | 3 | // 1 * 2 + 3 4 | 5 | import BinaryExpression from "./src/BinaryExpression.mjs" 6 | import NumericLiteral from "./src/NumericLiteral.mjs" 7 | import Visitor from "./src/Visitor.mjs" 8 | 9 | 10 | const visitor = new Visitor() 11 | 12 | 13 | const oneLiteral = new NumericLiteral(1) 14 | const twoLiteral = new NumericLiteral(2) 15 | const threeLiteral = new NumericLiteral(3) 16 | // console.log("NumericLiteral", oneLiteral.accept(visitor)) 17 | 18 | 19 | const leftExpression = new BinaryExpression(oneLiteral, '*', twoLiteral) 20 | const mainExpression = new BinaryExpression(leftExpression, '+', threeLiteral) 21 | 22 | // const binExpression = new BinaryExpression(oneLiteral, '+', twoLiteral) 23 | // const binExpression2 = new BinaryExpression(oneLiteral, '-', twoLiteral) 24 | 25 | 26 | // console.log("ADD", binExpression.accept(visitor)) 27 | // console.log("SUBTRACT", binExpression2.accept(visitor)) 28 | // console.log("MULTIPLY", binExpression3.accept(visitor)) 29 | 30 | console.log(mainExpression) 31 | console.log(mainExpression.accept(visitor)) -------------------------------------------------------------------------------- /design-patterns/visitor-pattern/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "visitor-pattern", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC" 12 | } 13 | -------------------------------------------------------------------------------- /design-patterns/visitor-pattern/src/BinaryExpression.mjs: -------------------------------------------------------------------------------- 1 | export default class BinaryExpression { 2 | constructor(left, operator, right) { 3 | this.left = left 4 | this.operator = operator 5 | this.right = right 6 | } 7 | 8 | accept(visitor) { 9 | return visitor.visitBinaryExpression(this) 10 | } 11 | } -------------------------------------------------------------------------------- /design-patterns/visitor-pattern/src/NumericLiteral.mjs: -------------------------------------------------------------------------------- 1 | export default class NumericLiteral { 2 | constructor(value) { 3 | this.value = value 4 | } 5 | 6 | accept(visitor) { 7 | return visitor.visitNumericLiteral(this) 8 | } 9 | } -------------------------------------------------------------------------------- /design-patterns/visitor-pattern/src/Visitor.mjs: -------------------------------------------------------------------------------- 1 | export default class Visitor { 2 | 3 | visitBinaryExpression(binExpr) { 4 | switch (binExpr.operator) { 5 | case "+": 6 | return binExpr.left.accept(this) + binExpr.right.accept(this) 7 | case "-": 8 | return binExpr.left.accept(this) - binExpr.right.accept(this) 9 | case "*": 10 | return binExpr.left.accept(this) * binExpr.right.accept(this) 11 | 12 | } 13 | } 14 | 15 | visitNumericLiteral(litExpr) { 16 | return litExpr.value 17 | } 18 | } -------------------------------------------------------------------------------- /dsa-questions/leetcode/best-time-to-buy-and-sell-stock-ii/best-time-to-buy-and-sell-stock-ii.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @see https://leetcode.com/problems/best-time-to-buy-and-sell-stock-ii/ 3 | */ 4 | 5 | // Time Complexity: O(N), where N is the length of the prices array. We only make one pass through the array. 6 | // Space Complexity: O(1). We only use a few extra variables. 7 | 8 | /** 9 | * @param {number[]} prices 10 | * @return {number} 11 | */ 12 | var maxProfit = function (prices) { 13 | let totalProfit = 0; 14 | 15 | // Iterate through the array 16 | for (let day = 1; day < prices.length; day++) { 17 | // If the price today is higher than the price yesterday 18 | if (prices[day] > prices[day - 1]) { 19 | // Add the profit to the total profit 20 | totalProfit += prices[day] - prices[day - 1]; 21 | } 22 | } 23 | 24 | return totalProfit; 25 | }; 26 | -------------------------------------------------------------------------------- /dsa-questions/leetcode/climbing-stairs/climbing-stairs.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @see https://leetcode.com/problems/climbing-stairs/ 3 | */ 4 | 5 | // Time Complexity: O(2^N) - Exponential time complexity (inefficient for large N) 6 | // This approach leads to Time Limit Exceeded (TLE) for larger inputs. 7 | 8 | /** 9 | * @param {number} n - The number of steps to reach the top 10 | * @return {number} - The number of distinct ways to climb to the top 11 | */ 12 | var climbStairs = function (n) { 13 | // Base case: If there are 0 or 1 steps, there's only 1 way to reach the top 14 | if (n === 0 || n === 1) return 1; 15 | 16 | // Recursively calculate the number of ways to climb when taking 1 step or 2 steps 17 | let climbOneStep = climbStairs(n - 1); 18 | let climbTwoSteps = climbStairs(n - 2); 19 | 20 | // The total number of ways to reach the top is the sum of the two possibilities 21 | return climbOneStep + climbTwoSteps; 22 | }; 23 | 24 | // Optimized solution using memoization to reduce time complexity from O(2^N) to O(N) 25 | let map = new Map(); // Map to store already calculated results for subproblems 26 | 27 | /** 28 | * @param {number} n - The number of steps to reach the top 29 | * @return {number} - The number of distinct ways to climb to the top 30 | */ 31 | var climbStairs = function (n) { 32 | // Base case: If there are 0 or 1 steps, there's only 1 way to reach the top 33 | if (n === 0 || n === 1) return 1; 34 | 35 | // If the result for the current number of steps is already calculated, return it 36 | if (map.has(n)) { 37 | return map.get(n); 38 | } 39 | 40 | // Recursively calculate the number of ways to climb when taking 1 step or 2 steps 41 | let climbOneStep = climbStairs(n - 1); 42 | let climbTwoSteps = climbStairs(n - 2); 43 | let totalWays = climbOneStep + climbTwoSteps; 44 | 45 | // Store the result in the map to avoid redundant calculations in future calls 46 | map.set(n, totalWays); 47 | 48 | // Return the total number of ways to reach the top 49 | return totalWays; 50 | }; 51 | -------------------------------------------------------------------------------- /dsa-questions/leetcode/combination-sum/combination-sum.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @see https://leetcode.com/problems/combination-sum/ 3 | */ 4 | 5 | // TODO need to check more accurate TC 6 | // TC O(2^N * N) 7 | // Picking and not picking will make 2^N and copying for the base case would make N 8 | 9 | /** 10 | * @param {number[]} candidates - The array of candidate numbers 11 | * @param {number} target - The target sum we need to achieve 12 | * @return {number[][]} - A list of all unique combinations that sum up to the target 13 | */ 14 | var combinationSum = function (candidates, target) { 15 | const ans = []; // Store all the valid combinations here 16 | helper(candidates, target, 0, [], ans); // Start the recursive process 17 | return ans; 18 | }; 19 | 20 | /** 21 | * Recursive helper function to generate combinations 22 | * 23 | * @param {number[]} candidates - The input array of numbers 24 | * @param {number} target - The remaining sum we need to achieve 25 | * @param {number} index - The current index in the array we're considering 26 | * @param {number[]} combinationsFormedSoFar - The current combination being formed 27 | * @param {number[][]} ans - The final array to store all valid combinations 28 | */ 29 | function helper(candidates, target, index, combinationsFormedSoFar, ans) { 30 | // Base case: If the remaining target is 0, we've found a valid combination 31 | if (target === 0) { 32 | ans.push([...combinationsFormedSoFar]); // Add a copy of the current combination to ans 33 | return; 34 | } 35 | 36 | // If the remaining target is negative or we've exhausted all candidates, stop the recursion 37 | if (target < 0 || index === candidates.length) { 38 | return; 39 | } 40 | 41 | // Recursive case 1: Do not pick the current candidate 42 | helper(candidates, target, index + 1, combinationsFormedSoFar, ans); 43 | 44 | // Recursive case 2: Pick the current candidate and continue 45 | combinationsFormedSoFar.push(candidates[index]); // Add the current candidate to the combination 46 | helper( 47 | candidates, 48 | target - candidates[index], // Reduce the target by the current candidate's value 49 | index, // Stay at the current index to allow repeated use of the same candidate 50 | combinationsFormedSoFar, 51 | ans 52 | ); 53 | 54 | // Backtrack by removing the last element to restore the previous state 55 | combinationsFormedSoFar.pop(); 56 | } 57 | -------------------------------------------------------------------------------- /dsa-questions/leetcode/contains-duplicate/contains-duplicate.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @see https://leetcode.com/problems/contains-duplicate/ 3 | */ 4 | 5 | // Time Complexity: O(N log N), where N is the number of elements in the array. This is due to the sorting operation, which dominates the complexity. The iteration through the array is O(N), but sorting is the most time-consuming operation here. 6 | // Space Complexity: O(1), assuming the sorting is done in-place. 7 | 8 | /** 9 | * Checks if there are any duplicate values in the given array. 10 | * 11 | * @param {number[]} nums - The input array of integers. 12 | * @return {boolean} - Returns true if there are duplicates, false otherwise. 13 | */ 14 | var containsDuplicate = function (nums) { 15 | // Sort the array to bring duplicate elements next to each other 16 | nums.sort((a, b) => a - b); 17 | 18 | // Iterate through the sorted array 19 | for (let index = 1; index < nums.length; index++) { 20 | // Check if the current value is the same as the previous value 21 | const currentValue = nums[index]; 22 | if (currentValue === nums[index - 1]) { 23 | // If duplicate is found, return true 24 | return true; 25 | } 26 | } 27 | 28 | // If no duplicates are found, return false 29 | return false; 30 | }; 31 | -------------------------------------------------------------------------------- /dsa-questions/leetcode/delete-node-in-a-linked-list/delete-node-in-a-linked-list.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @see https://leetcode.com/problems/delete-node-in-a-linked-list/ 3 | */ 4 | 5 | // Time Complexity: O(1) 6 | /** 7 | * Definition for singly-linked list. 8 | * function ListNode(val) { 9 | * this.val = val; 10 | * this.next = null; 11 | * } 12 | */ 13 | /** 14 | * @param {ListNode} node 15 | * @return {void} Do not return anything, modify node in-place instead. 16 | */ 17 | var deleteNode = function (node) { 18 | // Copy the value of the next node to the current node 19 | node.val = node.next.val; 20 | 21 | // Skip over the next node 22 | node.next = node.next.next; 23 | }; 24 | -------------------------------------------------------------------------------- /dsa-questions/leetcode/first-unique-character-in-a-string/first-unique-character-in-a-string.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @see https://leetcode.com/problems/first-unique-character-in-a-string/ 3 | */ 4 | 5 | // Time Complexity: O(n) 6 | // Space Complexity: O(1) (constant space) in terms of the alphabet since we're only dealing with lowercase English letters (a fixed set of 26 characters). 7 | 8 | /** 9 | * @param {string} s 10 | * @return {number} 11 | */ 12 | var firstUniqChar = function (s) { 13 | let charMap = new Map(); 14 | 15 | for (let char of s) { 16 | charMap.set(char, (charMap.get(char) ?? 0) + 1); 17 | } 18 | 19 | for (let index = 0; index < s.length; index++) { 20 | if (charMap.get(s[index]) === 1) { 21 | return index; 22 | } 23 | } 24 | 25 | return -1; 26 | }; 27 | -------------------------------------------------------------------------------- /dsa-questions/leetcode/linked-list-cycle/linked-list-cycle.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @see https://leetcode.com/problems/linked-list-cycle/ 3 | */ 4 | 5 | // Time Complexity: O(n) 6 | /** 7 | * Definition for singly-linked list. 8 | * function ListNode(val) { 9 | * this.val = val; 10 | * this.next = null; 11 | * } 12 | */ 13 | 14 | /** 15 | * @param {ListNode} head 16 | * @return {boolean} 17 | */ 18 | var hasCycle = function (head) { 19 | let slow = head, 20 | fast = head; 21 | 22 | while (fast && fast.next) { 23 | slow = slow.next; 24 | fast = fast.next.next; 25 | if (slow === fast) { 26 | return true; 27 | } 28 | } 29 | 30 | return false; 31 | }; 32 | -------------------------------------------------------------------------------- /dsa-questions/leetcode/matrix-diagonal-sum/matrix-diagonal-sum.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @see https://leetcode.com/problems/matrix-diagonal-sum/ 3 | */ 4 | 5 | // TC - O(N) 6 | /** 7 | * The function calculates the sum of the primary and secondary diagonals of a square matrix. 8 | * The primary diagonal runs from the top-left to the bottom-right. 9 | * The secondary diagonal runs from the top-right to the bottom-left. 10 | * If the matrix has an odd number of rows, the center element (which appears in both diagonals) is only counted once. 11 | * 12 | * @param {number[][]} mat - The input square matrix. 13 | * @return {number} - The sum of the diagonals. 14 | */ 15 | var diagonalSum = function (mat) { 16 | let r = mat.length; // Number of rows 17 | let c = mat[0].length; // Number of columns 18 | 19 | let sum = 0; 20 | for (let rowIndex = 0; rowIndex < r; rowIndex++) { 21 | // Get the primary diagonal element 22 | let primaryDiagonalElement = mat[rowIndex][rowIndex]; 23 | sum += primaryDiagonalElement; 24 | 25 | // Get the secondary diagonal element 26 | let lastRowElement = c - 1; 27 | let secondaryDiagonalElement = mat[rowIndex][lastRowElement - rowIndex]; 28 | 29 | // If the element is not the same as the primary diagonal element, add it 30 | if (rowIndex !== lastRowElement - rowIndex) { 31 | sum += secondaryDiagonalElement; 32 | } 33 | } 34 | 35 | return sum; 36 | }; 37 | -------------------------------------------------------------------------------- /dsa-questions/leetcode/maximum-depth-of-binary-tree/maximum-depth-of-binary-tree.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @see https://leetcode.com/problems/maximum-depth-of-binary-tree/ 3 | */ 4 | 5 | /* 6 | Time Complexity: O(n), where n is the number of nodes in the binary tree. Each node is visited exactly once. 7 | Space Complexity: O(h), where h is the height of the binary tree. This space is used by the recursion stack. In the worst case (if the tree is completely unbalanced), this could be O(n). 8 | */ 9 | /** 10 | * Definition for a binary tree node. 11 | * function TreeNode(val, left, right) { 12 | * this.val = (val===undefined ? 0 : val); 13 | * this.left = (left===undefined ? null : left); 14 | * this.right = (right===undefined ? null : right); 15 | * } 16 | */ 17 | 18 | /** 19 | * @param {TreeNode} root 20 | * @return {number} 21 | */ 22 | var maxDepth = function (root) { 23 | // Base case: if the root is null, return 0 24 | if (root === null) { 25 | return 0; 26 | } 27 | 28 | // Recursively find the depth of the left and right subtrees 29 | let leftDepth = maxDepth(root.left); 30 | let rightDepth = maxDepth(root.right); 31 | 32 | // The maximum depth is 1 + the maximum of the left and right depths 33 | return 1 + Math.max(leftDepth, rightDepth); 34 | }; 35 | -------------------------------------------------------------------------------- /dsa-questions/leetcode/merge-two-sorted-lists/merge-two-sorted-lists.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @see https://leetcode.com/problems/merge-two-sorted-lists/ 3 | */ 4 | 5 | // TC O(n + m) 6 | /** 7 | * Definition for singly-linked list. 8 | * function ListNode(val, next) { 9 | * this.val = (val===undefined ? 0 : val) 10 | * this.next = (next===undefined ? null : next) 11 | * } 12 | */ 13 | /** 14 | * @param {ListNode} list1 15 | * @param {ListNode} list2 16 | * @return {ListNode} 17 | */ 18 | var mergeTwoLists = function (list1, list2) { 19 | // Create a dummy node to serve as the start of the merged list 20 | let dummyHead = new ListNode(); 21 | let current = dummyHead; 22 | 23 | // Traverse both lists 24 | while (list1 !== null && list2 !== null) { 25 | if (list1.val <= list2.val) { 26 | // If list1's node value is smaller or equal, append it to the merged list 27 | current.next = list1; 28 | list1 = list1.next; // Move to the next node in list1 29 | } else { 30 | // If list2's node value is smaller, append it to the merged list 31 | current.next = list2; 32 | list2 = list2.next; // Move to the next node in list2 33 | } 34 | current = current.next; // Move to the next node in the merged list 35 | } 36 | 37 | // If one of the lists is exhausted, append the remaining part of the other list 38 | if (list1 !== null) { 39 | current.next = list1; 40 | } else { 41 | current.next = list2; 42 | } 43 | 44 | // The merged list starts from the next node of the dummy node 45 | return dummyHead.next; 46 | }; 47 | -------------------------------------------------------------------------------- /dsa-questions/leetcode/move-zeroes/move-zeroes.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @see https://leetcode.com/problems/move-zeroes/ 3 | */ 4 | 5 | // Time Complexity: O(n), where n is the length of the array. Each element is processed once 6 | /** 7 | * Moves all zeros in the array to the end while maintaining the relative order of non-zero elements. 8 | * 9 | * @param {number[]} nums - The input array of integers. 10 | * @return {void} - The function modifies the array in place and does not return anything. 11 | */ 12 | var moveZeroes = function (nums) { 13 | let lastNonZeroIndex = 0; // Pointer for the position of the next non-zero element 14 | 15 | // Iterate through the array 16 | let i = 0; 17 | while (i < nums.length) { 18 | if (nums[i] !== 0) { 19 | // Swap the current non-zero element with the element at lastNonZeroIndex 20 | [nums[i], nums[lastNonZeroIndex]] = [nums[lastNonZeroIndex], nums[i]]; 21 | lastNonZeroIndex++; 22 | } 23 | i++; 24 | } 25 | }; 26 | -------------------------------------------------------------------------------- /dsa-questions/leetcode/palindrome-linked-list/palindrome-linked-list.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @see https://leetcode.com/problems/palindrome-linked-list/ 3 | */ 4 | 5 | // Time Complexity: O(n) 6 | /** 7 | * Definition for singly-linked list. 8 | * function ListNode(val, next) { 9 | * this.val = (val===undefined ? 0 : val) 10 | * this.next = (next===undefined ? null : next) 11 | * } 12 | */ 13 | 14 | /** 15 | * @param {ListNode} head 16 | * @return {boolean} 17 | */ 18 | var isPalindrome = function (head) { 19 | if (!head || !head.next) return true; // A list with 0 or 1 node is a palindrome 20 | 21 | // Step 1: Find the middle of the linked list 22 | let slow = head, 23 | fast = head; 24 | while (fast && fast.next) { 25 | slow = slow.next; 26 | fast = fast.next.next; 27 | } 28 | 29 | // Step 2: Reverse the second half of the linked list 30 | let prev = null; 31 | while (slow) { 32 | let nextNode = slow.next; 33 | slow.next = prev; 34 | prev = slow; 35 | slow = nextNode; 36 | } 37 | 38 | // Step 3: Compare the first half and the reversed second half 39 | let left = head, 40 | right = prev; 41 | while (right) { 42 | // We only need to compare up to the end of the reversed second half 43 | if (left.val !== right.val) return false; 44 | left = left.next; 45 | right = right.next; 46 | } 47 | 48 | // Step 4 (Optional): Restore the original order of the second half (if needed) 49 | // -- This step is not necessary unless we need to keep the list intact. 50 | 51 | return true; 52 | }; 53 | -------------------------------------------------------------------------------- /dsa-questions/leetcode/plus-one/plus-one.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @see https://leetcode.com/problems/plus-one/ 3 | */ 4 | 5 | // Time Complexity: O(n) 6 | /** 7 | * @param {number[]} digits 8 | * @return {number[]} 9 | */ 10 | var plusOne = function (digits) { 11 | // Traverse the digits array from the end (least significant digit) to the beginning 12 | for (let i = digits.length - 1; i >= 0; i--) { 13 | // Check if the current digit is less than 9 14 | if (digits[i] < 9) { 15 | // If it is less than 9, simply increment this digit by one 16 | digits[i]++; 17 | // Return the updated digits array as no further carry is needed 18 | return digits; 19 | } 20 | 21 | // If the current digit is 9, setting it to 0 since 9 + 1 will generate a carry 22 | digits[i] = 0; 23 | } 24 | 25 | // After the loop, if all digits were 9 (e.g., [9, 9, 9]), all have been set to 0. 26 | // We need to add a new most significant digit to handle the carry (e.g., [1, 0, 0, 0]) 27 | // This step creates a new array with 1 followed by all zeroes. 28 | return [1, ...digits]; 29 | }; 30 | -------------------------------------------------------------------------------- /dsa-questions/leetcode/remove-duplicates-from-sorted-array/remove-duplicates-from-sorted-array.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @see https://leetcode.com/problems/remove-duplicates-from-sorted-array/ 3 | */ 4 | 5 | // Time Complexity: O(N) 6 | // Space Complexity: O(1) 7 | /** 8 | * @param {number[]} nums 9 | * @return {number} 10 | */ 11 | var removeDuplicates = function (nums) { 12 | if (nums.length === 0) return 0; 13 | 14 | let uniqueIndex = 0; // Pointer for the unique element's position 15 | 16 | // Iterate through the array 17 | for (let currentIndex = 1; currentIndex < nums.length; currentIndex++) { 18 | // If a new unique element is found 19 | if (nums[currentIndex] !== nums[uniqueIndex]) { 20 | uniqueIndex++; 21 | nums[uniqueIndex] = nums[currentIndex]; // Place the unique element at the uniqueIndex-th position 22 | } 23 | } 24 | 25 | // Return the count of unique elements 26 | return uniqueIndex + 1; 27 | }; 28 | -------------------------------------------------------------------------------- /dsa-questions/leetcode/remove-nth-node-from-end-of-list/remove-nth-node-from-end-of-list.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @see https://leetcode.com/problems/remove-nth-node-from-end-of-list/ 3 | */ 4 | 5 | // TC O(n) 6 | /** 7 | * Definition for singly-linked list. 8 | * function ListNode(val, next) { 9 | * this.val = (val===undefined ? 0 : val) 10 | * this.next = (next===undefined ? null : next) 11 | * } 12 | */ 13 | /** 14 | * Removes the nth node from the end of the list. 15 | * 16 | * @param {ListNode} head - The head of the linked list. 17 | * @param {number} n - The position from the end of the list to remove the node. 18 | * @return {ListNode} - The head of the modified list. 19 | */ 20 | var removeNthFromEnd = function (head, n) { 21 | // Create a dummy node that points to the head of the list. 22 | // This helps in handling edge cases, such as removing the head itself. 23 | let dummyHead = new ListNode(); 24 | dummyHead.next = head; 25 | 26 | // Initialize two pointers, both starting from the dummy node. 27 | let slow = dummyHead; 28 | let fast = dummyHead; 29 | 30 | // Move the fast pointer n+1 steps ahead to maintain the gap between fast and slow. 31 | while (n >= 0) { 32 | fast = fast.next; 33 | n--; 34 | } 35 | 36 | // Move both pointers until fast reaches the end of the list. 37 | while (fast != null) { 38 | fast = fast.next; 39 | slow = slow.next; 40 | } 41 | 42 | // Now, slow.next is the node to be removed. 43 | // Skip the node to remove it from the list. 44 | slow.next = slow.next.next; 45 | 46 | // Return the head of the modified list. 47 | return dummyHead.next; 48 | }; 49 | -------------------------------------------------------------------------------- /dsa-questions/leetcode/reverse-linked-list/reverse-linked-list.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @see https://leetcode.com/problems/reverse-linked-list/ 3 | */ 4 | 5 | // TC O(N) 6 | /** 7 | * Definition for singly-linked list. 8 | * function ListNode(val, next) { 9 | * this.val = (val===undefined ? 0 : val) 10 | * this.next = (next===undefined ? null : next) 11 | * } 12 | */ 13 | /** 14 | * @param {ListNode} head 15 | * @return {ListNode} 16 | */ 17 | var reverseList = function (head) { 18 | // Initialize previous as null and current as the head of the list 19 | let prev = null; 20 | let curr = head; 21 | 22 | // Traverse the list 23 | while (curr !== null) { 24 | // Temporarily store the next node 25 | let nextNode = curr.next; 26 | 27 | // Reverse the current node's pointer 28 | curr.next = prev; 29 | 30 | // Move prev and curr one step forward 31 | prev = curr; 32 | curr = nextNode; 33 | } 34 | 35 | // After the loop, prev will be pointing to the new head of the reversed list 36 | return prev; 37 | }; 38 | -------------------------------------------------------------------------------- /dsa-questions/leetcode/reverse-string/reverse-string.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @see https://leetcode.com/problems/reverse-string/ 3 | */ 4 | 5 | // Time Complexity: O(n) 6 | 7 | /** 8 | * @param {character[]} s 9 | * @return {void} Do not return anything, modify s in-place instead. 10 | */ 11 | var reverseString = function (s) { 12 | let start = 0; 13 | let end = s.length - 1; 14 | 15 | while (start < end) { 16 | [s[start], s[end]] = [s[end], s[start]]; 17 | start++; 18 | end--; 19 | } 20 | }; 21 | -------------------------------------------------------------------------------- /dsa-questions/leetcode/rotate-array/rotate-array.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @see https://leetcode.com/problems/rotate-array/ 3 | */ 4 | 5 | // Time Complexity: O(N), where N is the length of the array. Each element is reversed a constant number of times. 6 | // Space Complexity: O(1). The algorithm uses a constant amount of extra space. 7 | 8 | /** 9 | * Rotate the array to the right by k steps. 10 | * 11 | * @param {number[]} nums - The array to rotate. 12 | * @param {number} k - The number of steps to rotate the array. 13 | * @return {void} - The function modifies the array in place. 14 | */ 15 | var rotate = function (nums, k) { 16 | const n = nums.length; 17 | k = k % n; // In case k is greater than the length of the array 18 | 19 | // Reverse the entire array 20 | reverse(nums, 0, n - 1); 21 | // Reverse the first k elements 22 | reverse(nums, 0, k - 1); 23 | // Reverse the remaining elements 24 | reverse(nums, k, n - 1); 25 | }; 26 | 27 | /** 28 | * Helper function to reverse a portion of the array. 29 | * 30 | * @param {number[]} array - The array to reverse. 31 | * @param {number} start - The start index of the portion to reverse. 32 | * @param {number} end - The end index of the portion to reverse. 33 | */ 34 | function reverse(array, start, end) { 35 | while (start < end) { 36 | [array[start], array[end]] = [array[end], array[start]]; 37 | start++; 38 | end--; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /dsa-questions/leetcode/rotate-image/rotate-image.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @see https://leetcode.com/problems/rotate-image/ 3 | */ 4 | 5 | // Time Complexity: O(n^2) 6 | // Space Complexity: O(1) 7 | /** 8 | * Rotates the input matrix by 90 degrees clockwise in-place. 9 | * The approach involves two steps: 10 | * 1. Transpose the matrix. 11 | * 2. Reverse each row to achieve the desired rotation. 12 | * 13 | * @param {number[][]} matrix - 2D array representing the matrix to be rotated. 14 | * @return {void} Do not return anything, modify matrix in-place instead. 15 | */ 16 | var rotate = function (matrix) { 17 | // Step 1: Transpose the matrix 18 | // In a transpose, we convert rows into columns. 19 | // We swap the elements at (rowIndex, colIndex) with (colIndex, rowIndex). 20 | for (let rowIndex = 0; rowIndex < matrix.length; rowIndex++) { 21 | // Notice that the inner loop runs only for colIndex < rowIndex 22 | // to avoid unnecessary swaps and to handle only one side of the diagonal. 23 | for (let colIndex = 0; colIndex < rowIndex; colIndex++) { 24 | // Swap elements across the diagonal 25 | [matrix[rowIndex][colIndex], matrix[colIndex][rowIndex]] = [ 26 | matrix[colIndex][rowIndex], 27 | matrix[rowIndex][colIndex], 28 | ]; 29 | } 30 | } 31 | 32 | // Step 2: Reverse each row 33 | // After transposing, reverse each row to achieve the 90-degree rotation. 34 | for (let rowIndex = 0; rowIndex < matrix.length; rowIndex++) { 35 | matrix[rowIndex].reverse(); 36 | } 37 | }; 38 | -------------------------------------------------------------------------------- /dsa-questions/leetcode/single-number/single-number.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @see https://leetcode.com/problems/single-number/ 3 | */ 4 | 5 | // Time Complexity: O(N), where N is the number of elements in the array. We are iterating through the array once. 6 | // Space Complexity: O(1), since we are using only a constant amount of extra space 7 | /** 8 | * Finds the single number in the array where every other number appears twice. 9 | * 10 | * @param {number[]} nums - The input array of integers. 11 | * @return {number} - The single number that does not have a duplicate. 12 | */ 13 | var singleNumber = function (nums) { 14 | // Initialize the result variable 15 | let result = 0; 16 | 17 | // Iterate through the array 18 | for (let num of nums) { 19 | // Apply XOR operation 20 | result ^= num; 21 | } 22 | 23 | // Return the single number 24 | return result; 25 | }; 26 | -------------------------------------------------------------------------------- /dsa-questions/leetcode/subsets/subsets.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @see https://leetcode.com/problems/subsets/ 3 | */ 4 | 5 | // TC O(2^N * N) 6 | // Picking and not picking will make 2^N and copying for the base case would make N 7 | 8 | /** 9 | * @param {number[]} nums 10 | * @return {number[][]} 11 | */ 12 | var subsets = function (nums) { 13 | const ans = []; // Store all the subsets here 14 | helper(nums, 0, [], ans); // Start the recursive process 15 | return ans; 16 | }; 17 | 18 | /** 19 | * Recursive helper function to generate subsets 20 | * 21 | * @param {number[]} nums - The input array of numbers 22 | * @param {number} index - The current index in the array we're considering 23 | * @param {number[]} combinationsFormedSoFar - The current subset being formed 24 | * @param {number[][]} ans - The final array to store all subsets 25 | */ 26 | function helper(nums, index, combinationsFormedSoFar, ans) { 27 | // Base case: If we've considered all elements 28 | if (index === nums.length) { 29 | // Note: In JavaScript, arrays are passed by reference 30 | ans.push([...combinationsFormedSoFar]); // Add a copy of the current subset to ans 31 | return; 32 | } 33 | 34 | // Recursive case 1: Do not pick the current element 35 | helper(nums, index + 1, combinationsFormedSoFar, ans); 36 | 37 | // Recursive case 2: Pick the current element 38 | combinationsFormedSoFar.push(nums[index]); // Add the current element to the subset 39 | helper(nums, index + 1, combinationsFormedSoFar, ans); 40 | 41 | // Backtrack by removing the last element to restore the previous state 42 | combinationsFormedSoFar.pop(); 43 | } 44 | -------------------------------------------------------------------------------- /dsa-questions/leetcode/symmetric-tree/symmetric-tree.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @see https://leetcode.com/problems/symmetric-tree/ 3 | */ 4 | 5 | /* 6 | Time Complexity is O(n), where n is the number of nodes in the binary tree. 7 | Space Complexity is O(h), where h is the height of the binary tree 8 | */ 9 | 10 | /** 11 | * Definition for a binary tree node. 12 | * function TreeNode(val, left, right) { 13 | * this.val = (val===undefined ? 0 : val) 14 | * this.left = (left===undefined ? null : left) 15 | * this.right = (right===undefined ? null : right) 16 | * } 17 | */ 18 | /** 19 | * @param {TreeNode} root 20 | * @return {boolean} 21 | */ 22 | var isSymmetric = function (root) { 23 | return isMirror(root, root); 24 | }; 25 | 26 | /** 27 | * Helper function to determine if two trees are mirror images of each other. 28 | * 29 | * @param {TreeNode} tree1 - The root node of the first tree. 30 | * @param {TreeNode} tree2 - The root node of the second tree. 31 | * @return {boolean} - Returns true if the trees are mirrors of each other, otherwise false. 32 | */ 33 | function isMirror(tree1, tree2) { 34 | // Both trees are empty, thus they are symmetric 35 | if (tree1 === null && tree2 === null) { 36 | return true; 37 | } 38 | 39 | // One tree is empty while the other is not, thus they are not symmetric 40 | if (tree1 === null || tree2 === null) { 41 | return false; 42 | } 43 | 44 | // The root values are different, thus they are not symmetric 45 | if (tree1.val !== tree2.val) { 46 | return false; 47 | } 48 | 49 | // Recursively check if the left subtree of tree1 is a mirror of the right subtree of tree2 50 | // and if the right subtree of tree1 is a mirror of the left subtree of tree2 51 | return isMirror(tree1.left, tree2.right) && isMirror(tree1.right, tree2.left); 52 | } 53 | -------------------------------------------------------------------------------- /dsa-questions/leetcode/transpose-matrix/transpose-matrix.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @see https://leetcode.com/problems/transpose-matrix/ 3 | */ 4 | 5 | // TC - O(M*N) 6 | /** 7 | * Given a 2D matrix, this function returns its transpose. 8 | * The transpose of a matrix is obtained by swapping its rows and columns. 9 | * 10 | * @param {number[][]} matrix - The input 2D matrix. 11 | * @return {number[][]} - The transposed matrix. 12 | */ 13 | var transpose = function (matrix) { 14 | // Get the number of rows and columns in the original matrix 15 | let r = matrix.length; 16 | let c = matrix[0].length; 17 | 18 | // Create an empty output matrix with the dimensions swapped (rows become columns, columns become rows) 19 | let output = Array.from({ length: c }, () => Array(r).fill(0)); 20 | 21 | // Iterate through each element of the original matrix 22 | for (let rowIndex = 0; rowIndex < r; rowIndex++) { 23 | for (let colIndex = 0; colIndex < c; colIndex++) { 24 | // Assign the value at [rowIndex][colIndex] in the original matrix to [colIndex][rowIndex] in the output matrix 25 | output[colIndex][rowIndex] = matrix[rowIndex][colIndex]; 26 | } 27 | } 28 | 29 | // Return the transposed matrix 30 | return output; 31 | }; 32 | -------------------------------------------------------------------------------- /dsa-questions/leetcode/two-sum/two-sum.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @see https://leetcode.com/problems/two-sum/ 3 | */ 4 | 5 | // Time Complexity: O(n) 6 | // Space Complexity: O(n) 7 | /** 8 | * Find two indices in the array such that their values add up to the target. 9 | * 10 | * @param {number[]} nums - Array of integers. 11 | * @param {number} target - The target sum. 12 | * @return {number[]} - The indices of the two numbers that add up to the target. 13 | */ 14 | var twoSum = function (nums, target) { 15 | // Create a Map to store numbers and their indices 16 | const numToIndex = new Map(); 17 | 18 | // Iterate over each number in the array 19 | for (let i = 0; i < nums.length; i++) { 20 | const currentNum = nums[i]; 21 | const complement = target - currentNum; 22 | 23 | // Check if the complement (target - currentNum) exists in the Map 24 | if (numToIndex.has(complement)) { 25 | // If it exists, return the indices of the complement and the current number 26 | return [numToIndex.get(complement), i]; 27 | } 28 | 29 | // If the complement does not exist in the Map, store the current number and its index 30 | numToIndex.set(currentNum, i); 31 | } 32 | 33 | // If no solution is found, return an empty array (should not be reached with valid input) 34 | return []; 35 | }; 36 | -------------------------------------------------------------------------------- /dsa-questions/leetcode/valid-anagram/valid-anagram.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @see https://leetcode.com/problems/valid-anagram/ 3 | */ 4 | 5 | // Time Complexity: O(n) 6 | /** 7 | * Determines if two strings are anagrams of each other. 8 | * 9 | * @param {string} s - The first string. 10 | * @param {string} t - The second string. 11 | * @return {boolean} - Returns true if the strings are anagrams, otherwise false. 12 | */ 13 | var isAnagram = function (s, t) { 14 | // If the lengths are different, they cannot be anagrams 15 | if (s.length !== t.length) { 16 | return false; 17 | } 18 | 19 | // Initialize an array to count character frequencies (for 26 lowercase English letters) 20 | const charCount = new Array(26).fill(0); 21 | 22 | // Increment the count for each character in the first string 23 | for (let i = 0; i < s.length; i++) { 24 | charCount[s.charCodeAt(i) - 97]++; 25 | } 26 | 27 | // Decrement the count for each character in the second string 28 | for (let i = 0; i < t.length; i++) { 29 | charCount[t.charCodeAt(i) - 97]--; 30 | } 31 | 32 | // If any count is not zero, the strings are not anagrams 33 | for (let count of charCount) { 34 | if (count !== 0) { 35 | return false; 36 | } 37 | } 38 | 39 | // If all counts are zero, the strings are anagrams 40 | return true; 41 | }; 42 | -------------------------------------------------------------------------------- /dsa-questions/leetcode/valid-palindrome/valid-palindrome.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @see https://leetcode.com/problems/valid-palindrome/ 3 | */ 4 | 5 | // Time Complexity: O(n): The string is processed twice (once for filtering and once for palindrome checking), where n is the length of the string. 6 | // Space Complexity: O(n): The filtered string is stored in a new variable. 7 | /** 8 | * @param {string} s 9 | * @return {boolean} 10 | */ 11 | var isPalindrome = function (s) { 12 | let cleanedS = s.toLowerCase().replace(/[^a-z0-9]/g, ""); 13 | 14 | let left = 0; 15 | let right = cleanedS.length - 1; 16 | 17 | while (left < right) { 18 | if (cleanedS[left] !== cleanedS[right]) { 19 | return false; 20 | } 21 | left++; 22 | right--; 23 | } 24 | 25 | return true; 26 | }; 27 | -------------------------------------------------------------------------------- /dsa-questions/leetcode/validate-binary-search-tree/validate-binary-search-tree.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @see https://leetcode.com/problems/validate-binary-search-tree/description/ 3 | */ 4 | 5 | /* 6 | Time Complexity is O(n), where n is the number of nodes in the binary tree. 7 | Space Complexity is O(h), where h is the height of the binary tree 8 | */ 9 | 10 | /** 11 | * Definition for a binary tree node. 12 | * function TreeNode(val, left, right) { 13 | * this.val = (val===undefined ? 0 : val) 14 | * this.left = (left===undefined ? null : left) 15 | * this.right = (right===undefined ? null : right) 16 | * } 17 | */ 18 | /** 19 | * @param {TreeNode} root 20 | * @return {boolean} 21 | */ 22 | var isValidBST = function (root) { 23 | return isBST(root, -Infinity, Infinity); 24 | }; 25 | 26 | function isBST(node, lowerBound, upperBound) { 27 | // Base case: An empty tree is a valid BST 28 | if (node === null) { 29 | return true; 30 | } 31 | 32 | // Check current node value against the valid range 33 | if (node.val <= lowerBound || node.val >= upperBound) { 34 | return false; 35 | } 36 | 37 | // Recursively check the left and right subtrees with updated ranges 38 | return ( 39 | isBST(node.left, lowerBound, node.val) && 40 | isBST(node.right, node.val, upperBound) 41 | ); 42 | } 43 | -------------------------------------------------------------------------------- /frontend-machine-coding/README.md: -------------------------------------------------------------------------------- 1 | # Frontend Machine Coding Interview Questions in Javascript 2 | 3 | | Problem | Video | Solution | 4 | | --------------------- | ------------------------------------- | ------------------------------- | 5 | | Star Rating Component | [Video](https://youtu.be/QsvPgVV_1Os) | [Solution](./star-rating) | 6 | | Modal Component | [Video](https://youtu.be/E6z42PHIuLA) | [Solution](./modal-component) | 7 | | Carousel Component | [Video](https://youtu.be/qX5qjRnHSnc) | [Solution](./carousel) | 8 | | Accordion Component | [Video](https://youtu.be/iQpX0W18Yb0) | [Solution](./accordion) | 9 | | OTP Input Component | [Video](https://youtu.be/dAvy4OYZpHk) | [Solution](./otp-input) | 10 | | Bishop on Chessboard | [Video](https://youtu.be/aY2ra2BDBd8) | [Solution](./bishop-chessboard) | 11 | | Tabs Component | [Video](https://youtu.be/Q-oCO7so_lc) | [Solution](./tabs) | 12 | | Tic Tac Toe | [Video](https://youtu.be/5u-ENEknLXs) | [Solution](./tic-tac-toe) | 13 | | Todo List | [Video]() | [Solution](./todo-list/) | 14 | 15 | You can check out the above solutions live [here](https://frontend-machine-coding.vercel.app/) 16 | -------------------------------------------------------------------------------- /frontend-machine-coding/accordion/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Accordion 7 | 8 | 9 | 10 | 11 |
12 | 13 | -------------------------------------------------------------------------------- /frontend-machine-coding/accordion/scripts/index.js: -------------------------------------------------------------------------------- 1 | import { Accordion } from "./lib.js"; 2 | 3 | Accordion({ 4 | container: ".accordion-container", 5 | data: [ 6 | { 7 | title: "Section 1", 8 | content: 9 | "

Lorem ipsum dolor sit amet consectetur, adipisicing elit. Explicabo quasi officiis, voluptatem id molestiae maiores aliquam ipsa harum, in ratione voluptas! Provident at ducimus nam reiciendis reprehenderit, labore fugit libero.

", 10 | }, 11 | 12 | { 13 | title: "Section 2", 14 | content: 15 | "

Lorem ipsum dolor sit amet consectetur, adipisicing elit. Explicabo quasi officiis, voluptatem id molestiae maiores aliquam ipsa harum, in ratione voluptas! Provident at ducimus nam reiciendis reprehenderit, labore fugit libero.

", 16 | }, 17 | ], 18 | }); 19 | -------------------------------------------------------------------------------- /frontend-machine-coding/accordion/scripts/lib.js: -------------------------------------------------------------------------------- 1 | let defaultConfig = { 2 | container: ".accordion-container", 3 | data: [ 4 | { 5 | title: "title1", 6 | content: "content", 7 | }, 8 | ], 9 | }; 10 | 11 | let container; 12 | 13 | export function Accordion(config) { 14 | config = JSON.parse(JSON.stringify(config)); 15 | Object.keys(defaultConfig).forEach((key) => { 16 | defaultConfig[key] = config[key]; 17 | }); 18 | 19 | createAccordion(); 20 | } 21 | function createAccordion() { 22 | container = document.querySelector(defaultConfig.container); 23 | 24 | if (!container) return; 25 | 26 | let frag = document.createDocumentFragment(); 27 | 28 | defaultConfig.data.forEach(({ title, content }, index) => { 29 | let accordionEl = document.createElement("div"); 30 | let accordionFrag = document.createDocumentFragment(); 31 | 32 | let accordionHeader = document.createElement("div"); 33 | accordionHeader.classList.add("header"); 34 | accordionHeader.dataset.position = index; 35 | accordionHeader.innerHTML = title; 36 | 37 | let accordionContent = document.createElement("div"); 38 | accordionContent.classList.add("content"); 39 | accordionContent.innerHTML = content; 40 | 41 | accordionFrag.appendChild(accordionHeader); 42 | accordionFrag.appendChild(accordionContent); 43 | 44 | accordionEl.appendChild(accordionFrag); 45 | 46 | frag.appendChild(accordionEl); 47 | }); 48 | 49 | container.appendChild(frag); 50 | container.addEventListener("click", onClick); 51 | } 52 | 53 | function onClick(e) { 54 | let position = e.target.dataset.position; 55 | 56 | if (isNaN(position)) return; 57 | 58 | let headerElements = document.querySelectorAll(".header"); 59 | headerElements.forEach((headerElement) => { 60 | if (headerElement.getAttribute("data-position") === position) { 61 | headerElement.classList.toggle("active"); 62 | } 63 | }); 64 | } 65 | -------------------------------------------------------------------------------- /frontend-machine-coding/accordion/style.css: -------------------------------------------------------------------------------- 1 | .header { 2 | cursor: pointer; 3 | padding: 1rem; 4 | background-color: #eeeeee; 5 | border-bottom: solid 1px rgba(0, 0, 0, 0.1); 6 | user-select: none; 7 | } 8 | 9 | .header:hover { 10 | background-color: #cbcbcb; 11 | } 12 | .content { 13 | display: none; 14 | padding: 0 2rem; 15 | } 16 | 17 | .active ~ .content { 18 | display: block; 19 | } 20 | -------------------------------------------------------------------------------- /frontend-machine-coding/bishop-chessboard/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Bishop on Chessboard 7 | 8 | 9 | 10 | 11 |
12 | 13 | -------------------------------------------------------------------------------- /frontend-machine-coding/bishop-chessboard/scripts/index.js: -------------------------------------------------------------------------------- 1 | import { BishopChessboard } from "./lib.js"; 2 | 3 | new BishopChessboard({ 4 | container: ".bishop-chessboard", 5 | cellSize: "3rem", 6 | // count: 10, 7 | }); 8 | -------------------------------------------------------------------------------- /frontend-machine-coding/bishop-chessboard/scripts/lib.js: -------------------------------------------------------------------------------- 1 | let defaultConfig = { 2 | container: ".bishop-chessboard", 3 | count: 8, 4 | cellSize: "3rem", 5 | }; 6 | 7 | export class BishopChessboard { 8 | constructor(config) { 9 | this.config = JSON.parse(JSON.stringify(defaultConfig)); 10 | this.init(config); 11 | } 12 | 13 | init(config) { 14 | Object.keys(config).forEach((key) => { 15 | this.config[key] = config[key]; 16 | }); 17 | 18 | this.createChessBoard(); 19 | } 20 | 21 | createChessBoard() { 22 | this.container = document.querySelector(this.config.container); 23 | 24 | if (!this.container) return; 25 | let containerFrag = document.createDocumentFragment(); 26 | for (let rowIndex = 0; rowIndex < this.config.count; rowIndex++) { 27 | let row = document.createElement("tr"); 28 | let rowFrag = document.createDocumentFragment(); 29 | let isWhite = rowIndex % 2 == 0 ? true : false; 30 | 31 | for (let colIndex = 0; colIndex < this.config.count; colIndex++) { 32 | let cell = document.createElement("td"); 33 | cell.style.width = this.config.cellSize; 34 | cell.style.height = this.config.cellSize; 35 | 36 | cell.dataset.value = `${rowIndex}-${colIndex}`; 37 | 38 | if (isWhite) { 39 | cell.classList.add("white"); 40 | } else { 41 | cell.classList.add("black"); 42 | } 43 | isWhite = !isWhite; 44 | 45 | rowFrag.appendChild(cell); 46 | } 47 | row.appendChild(rowFrag); 48 | containerFrag.appendChild(row); 49 | } 50 | 51 | this.container.appendChild(containerFrag); 52 | this.container.addEventListener("mouseover", this.onMouseOver.bind(this)); 53 | this.container.addEventListener("mouseleave", this.onMouseLeave.bind(this)); 54 | } 55 | 56 | onMouseOver(e) { 57 | let value = e.target.dataset.value; 58 | 59 | if (!value) return; 60 | 61 | let [rowIndex, colIndex] = value.split("-").map(Number); 62 | let map = new Map(); 63 | map.set(value, true); 64 | 65 | this.findTopLeft(rowIndex, colIndex, map); 66 | this.findTopRight(rowIndex, colIndex, map); 67 | this.findBottomLeft(rowIndex, colIndex, map); 68 | this.findBottomRight(rowIndex, colIndex, map); 69 | 70 | let cells = document.querySelectorAll("td"); 71 | 72 | cells.forEach((cell) => { 73 | cell.classList.remove("orange"); 74 | }); 75 | 76 | cells.forEach((cell) => { 77 | let cellValue = cell.dataset.value; 78 | if (map.get(cellValue)) { 79 | cell.classList.add("orange"); 80 | } 81 | }); 82 | } 83 | onMouseLeave(e) { 84 | let cells = document.querySelectorAll("td"); 85 | cells.forEach((cell) => { 86 | cell.classList.remove("orange"); 87 | }); 88 | } 89 | 90 | findTopLeft(rowIndex, colIndex, map) { 91 | rowIndex--; 92 | colIndex--; 93 | 94 | while (rowIndex >= 0 && colIndex >= 0) { 95 | let cellValue = `${rowIndex}-${colIndex}`; 96 | map.set(cellValue, true); 97 | rowIndex--; 98 | colIndex--; 99 | } 100 | } 101 | findTopRight(rowIndex, colIndex, map) { 102 | rowIndex--; 103 | colIndex++; 104 | 105 | while (rowIndex >= 0 && colIndex < this.config.count) { 106 | let cellValue = `${rowIndex}-${colIndex}`; 107 | map.set(cellValue, true); 108 | rowIndex--; 109 | colIndex++; 110 | } 111 | } 112 | findBottomLeft(rowIndex, colIndex, map) { 113 | rowIndex++; 114 | colIndex--; 115 | 116 | while (rowIndex < this.config.count && colIndex >= 0) { 117 | let cellValue = `${rowIndex}-${colIndex}`; 118 | map.set(cellValue, true); 119 | rowIndex++; 120 | colIndex--; 121 | } 122 | } 123 | findBottomRight(rowIndex, colIndex, map) { 124 | rowIndex++; 125 | colIndex++; 126 | 127 | while (rowIndex < this.config.count && colIndex < this.config.count) { 128 | let cellValue = `${rowIndex}-${colIndex}`; 129 | map.set(cellValue, true); 130 | rowIndex++; 131 | colIndex++; 132 | } 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /frontend-machine-coding/bishop-chessboard/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | min-height: 100vh; 3 | display: flex; 4 | justify-content: center; 5 | align-items: center; 6 | background-color: #0093e9; 7 | background-image: linear-gradient(160deg, #0093e9 0%, #80d0c7 100%); 8 | } 9 | 10 | tr { 11 | display: flex; 12 | } 13 | 14 | .white { 15 | background-color: white; 16 | } 17 | 18 | .black { 19 | background-color: black; 20 | } 21 | 22 | .orange { 23 | background-color: orange; 24 | } 25 | -------------------------------------------------------------------------------- /frontend-machine-coding/carousel/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Carousel 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /frontend-machine-coding/carousel/scripts/index.js: -------------------------------------------------------------------------------- 1 | import { Carousel } from "./lib.js"; 2 | 3 | Carousel({ 4 | container: ".carousel-container", 5 | data: [ 6 | { 7 | src: "https://source.unsplash.com/a-cluster-of-stars-in-the-night-sky-w5N9CqsSnQ4", 8 | }, 9 | { 10 | src: "https://source.unsplash.com/a-couple-of-tall-buildings-next-to-each-other-9bLnvxJcxgg", 11 | }, 12 | { 13 | src: "https://source.unsplash.com/a-couple-of-people-that-are-standing-in-a-tunnel-tDqn8GeRgnU", 14 | }, 15 | ], 16 | timeout: 3000, 17 | leftToRight: false, 18 | }); 19 | -------------------------------------------------------------------------------- /frontend-machine-coding/carousel/scripts/lib.js: -------------------------------------------------------------------------------- 1 | let defaultConfig = { 2 | container: ".carousel-container", 3 | data: [ 4 | { 5 | src: "https://unsplash.com/photos/a-cluster-of-stars-in-the-night-sky-w5N9CqsSnQ4", 6 | }, 7 | ], 8 | timeout: 1000, 9 | leftToRight: true, 10 | }; 11 | 12 | let container; 13 | let imgElements; 14 | let activeImageIndex = 0; 15 | let VALUES = { 16 | NEXT: "next", 17 | PREV: "prev", 18 | }; 19 | let autoPlayOffset; 20 | 21 | export function Carousel(config) { 22 | config = JSON.parse(JSON.stringify(config)); 23 | Object.keys(config).forEach((key) => { 24 | defaultConfig[key] = config[key]; 25 | }); 26 | autoPlayOffset = defaultConfig.leftToRight ? 1 : -1; 27 | createCarousel(); 28 | } 29 | 30 | function createCarousel() { 31 | container = document.querySelector(defaultConfig.container); 32 | 33 | if (!container) return; 34 | 35 | let frag = document.createDocumentFragment(); 36 | 37 | defaultConfig.data.forEach(({ src }) => { 38 | let imgElement = document.createElement("img"); 39 | imgElement.src = src; 40 | frag.appendChild(imgElement); 41 | }); 42 | 43 | frag.children[activeImageIndex].classList.add("active"); 44 | 45 | let nextBtn = document.createElement("button"); 46 | nextBtn.textContent = ">"; 47 | nextBtn.dataset.value = VALUES.NEXT; 48 | nextBtn.classList.add(VALUES.NEXT); 49 | 50 | let prevBtn = document.createElement("button"); 51 | prevBtn.textContent = "<"; 52 | prevBtn.dataset.value = VALUES.PREV; 53 | prevBtn.classList.add(VALUES.PREV); 54 | 55 | frag.appendChild(prevBtn); 56 | frag.appendChild(nextBtn); 57 | 58 | container.appendChild(frag); 59 | 60 | imgElements = document.querySelectorAll("img"); 61 | 62 | container.addEventListener("click", onClick); 63 | autoPlay(); 64 | } 65 | 66 | function autoPlay() { 67 | setTimeout(autoPlay, defaultConfig.timeout); 68 | swapImages(autoPlayOffset); 69 | } 70 | 71 | function onClick(e) { 72 | let btnValue = e.target.dataset.value; 73 | 74 | if (!btnValue) return; 75 | 76 | let offset = btnValue === VALUES.NEXT ? 1 : -1; 77 | swapImages(offset); 78 | } 79 | 80 | function swapImages(offset) { 81 | imgElements.forEach((img) => { 82 | img.classList.remove("active"); 83 | }); 84 | 85 | if (activeImageIndex + offset >= defaultConfig.data.length) { 86 | activeImageIndex = 0; 87 | } else if (activeImageIndex + offset < 0) { 88 | activeImageIndex = defaultConfig.data.length - 1; 89 | } else { 90 | activeImageIndex = activeImageIndex + offset; 91 | } 92 | 93 | imgElements[activeImageIndex].classList.add("active"); 94 | } 95 | -------------------------------------------------------------------------------- /frontend-machine-coding/carousel/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | padding: 0; 4 | } 5 | 6 | .carousel-container { 7 | width: 100%; 8 | height: 100vh; 9 | position: relative; 10 | } 11 | 12 | .carousel-container > img { 13 | display: block; 14 | width: 100%; 15 | height: 100%; 16 | position: absolute; 17 | object-fit: cover; 18 | opacity: 0; 19 | transition: 200ms opacity ease; 20 | } 21 | 22 | .carousel-container > img.active { 23 | opacity: 1; 24 | } 25 | 26 | .carousel-container > button { 27 | position: absolute; 28 | color: white; 29 | font-size: 2rem; 30 | background: none; 31 | border: none; 32 | top: 50%; 33 | cursor: pointer; 34 | } 35 | 36 | .carousel-container > button.prev { 37 | left: 1rem; 38 | } 39 | 40 | .carousel-container > button.next { 41 | right: 1rem; 42 | } 43 | -------------------------------------------------------------------------------- /frontend-machine-coding/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Frontend Machine Coding 7 | 8 | 9 | 10 |

Machine Coding questions in Vanilla Javascript

11 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /frontend-machine-coding/kanban-board/components/Column/Column.js: -------------------------------------------------------------------------------- 1 | import { deleteColumn, deleteTask, getTasks } from "../../services/API.js"; 2 | import { ACTIONS, TYPES, getElementFromHtml } from "../../utils.js"; 3 | import { Task } from "../Task/Task.js"; 4 | 5 | export class Column { 6 | constructor(columnId, title) { 7 | this.domElement = this.getElement(columnId, title); 8 | this.tasksContainer = this.domElement.querySelector(".tasks"); 9 | this.init(columnId); 10 | } 11 | 12 | init(columnId) { 13 | this.setupListeners(); 14 | this.renderTasks(columnId); 15 | } 16 | 17 | getElement(columnId, title) { 18 | return getElementFromHtml(` 19 |
20 |
21 |

${title}

22 | 23 |
24 | 25 | 26 |
`); 27 | } 28 | 29 | setupListeners(e) { 30 | this.domElement.addEventListener("click", (e) => { 31 | let columnId = Number(e.target.dataset.columnId); 32 | let action = e.target.dataset.action; 33 | 34 | if (!columnId) return; 35 | if (!action) return; 36 | 37 | if (action === ACTIONS.DELETE_TASK) { 38 | let taskId = Number(e.target.dataset.taskId); 39 | 40 | if (!taskId) return; 41 | 42 | let task = e.target.previousElementSibling.textContent; 43 | let result = confirm(`Are you sure you want to delete ${task}?`); 44 | if (result) { 45 | deleteTask(columnId, taskId); 46 | e.target.parentElement.remove(); 47 | } 48 | } else if (action === ACTIONS.DELETE_COLUMN) { 49 | let column = e.target.previousElementSibling.textContent; 50 | 51 | let result = confirm(`Are you sure you want to delete ${column}?`); 52 | if (result) { 53 | deleteColumn(columnId); 54 | e.target.parentElement.parentElement.remove(); 55 | } 56 | } 57 | }); 58 | } 59 | renderTasks(columnId) { 60 | let frag = document.createDocumentFragment(); 61 | getTasks(columnId).forEach((task) => { 62 | let taskEl = new Task(columnId, task.taskId, task.content).domElement; 63 | frag.appendChild(taskEl); 64 | }); 65 | this.tasksContainer.appendChild(frag); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /frontend-machine-coding/kanban-board/components/Column/column.styles.css: -------------------------------------------------------------------------------- 1 | .column { 2 | height: 100%; 3 | background: #f4f4f4; 4 | box-shadow: 0px 5px 15px rgba(0, 0, 0, 0.25); 5 | text-align: center; 6 | padding: 1rem; 7 | border-radius: 3%; 8 | min-width: 18rem; 9 | height: fit-content; 10 | } 11 | 12 | .column-header { 13 | display: flex; 14 | justify-content: space-between; 15 | margin-bottom: 0.5rem; 16 | gap: 1rem; 17 | } 18 | 19 | .column-header h2 { 20 | overflow: auto; 21 | } 22 | 23 | .tasks { 24 | list-style: none; 25 | padding: 1rem; 26 | display: flex; 27 | flex-direction: column; 28 | gap: 1rem; 29 | max-height: 70vh; 30 | overflow: auto; 31 | min-height: 3rem; 32 | } 33 | 34 | .delete-column { 35 | background: none; 36 | border: none; 37 | cursor: pointer; 38 | } 39 | 40 | .add-task { 41 | width: 80%; 42 | padding: 0.5rem 0; 43 | border-radius: 3%; 44 | border: none; 45 | cursor: pointer; 46 | background-color: rgba(0, 0, 0, 0.1); 47 | } 48 | -------------------------------------------------------------------------------- /frontend-machine-coding/kanban-board/components/Kanban/Kanban.js: -------------------------------------------------------------------------------- 1 | import { getColumns, updateTaskPosition } from "../../services/API.js"; 2 | import { 3 | ACTIONS, 4 | ADD_COLUMN_EVENT, 5 | ADD_TASK_EVENT, 6 | TYPES, 7 | } from "../../utils.js"; 8 | import { Column } from "../Column/Column.js"; 9 | import { Overlay } from "../Overlay/Overlay.js"; 10 | 11 | export class Kanban { 12 | constructor(config) { 13 | this.config = config; 14 | this.createKanbanBoard(); 15 | } 16 | 17 | createKanbanBoard() { 18 | this.container = document.querySelector(this.config.container); 19 | if (!this.container) return; 20 | 21 | let frag = document.createDocumentFragment(); 22 | 23 | getColumns().forEach((column) => { 24 | let columnEl = new Column(column.columnId, column.title).domElement; 25 | frag.appendChild(columnEl); 26 | }); 27 | 28 | this.addColumnButton = document.createElement("button"); 29 | this.addColumnButton.textContent = "+"; 30 | this.addColumnButton.classList.add("add-column"); 31 | this.addColumnButton.addEventListener("click", this.addColumn.bind(this)); 32 | frag.appendChild(this.addColumnButton); 33 | 34 | this.container.appendChild(frag); 35 | 36 | this.overlayElement = new Overlay(this.container, this.addColumnButton); 37 | this.container.parentElement.appendChild(this.overlayElement.domElement); 38 | 39 | this.container.addEventListener("click", this.addTask.bind(this)); 40 | 41 | this.container.addEventListener("dragstart", this.onDragStart.bind(this)); 42 | this.container.addEventListener("dragover", (e) => { 43 | e.preventDefault(); 44 | }); 45 | this.container.addEventListener("dragend", this.onDragEnd.bind(this)); 46 | this.container.addEventListener("drop", this.onDrop.bind(this)); 47 | } 48 | 49 | addColumn(e) { 50 | this.overlayElement.domElement.dispatchEvent( 51 | new CustomEvent(ADD_COLUMN_EVENT, { 52 | detail: { 53 | action: ACTIONS.ADD_COLUMN, 54 | }, 55 | }) 56 | ); 57 | } 58 | 59 | addTask(e) { 60 | let columnId = Number(e.target.dataset.columnId); 61 | let action = e.target.dataset.action; 62 | 63 | if (action !== ACTIONS.ADD_TASK) return; 64 | if (!columnId) return; 65 | 66 | this.overlayElement.domElement.dispatchEvent( 67 | new CustomEvent(ADD_TASK_EVENT, { 68 | detail: { 69 | action: ACTIONS.ADD_TASK, 70 | columnId, 71 | }, 72 | }) 73 | ); 74 | } 75 | 76 | onDragStart(e) { 77 | e.dataTransfer.setData("text/plain", e.target.dataset.taskId); 78 | e.target.classList.add("is-dragging"); 79 | } 80 | 81 | onDragEnd(e) { 82 | e.target.classList.remove("is-dragging"); 83 | } 84 | 85 | onDrop(e) { 86 | e.preventDefault(); 87 | if (!e.target.classList.contains("tasks")) return; 88 | 89 | let tasksContainer = e.target; 90 | let column = tasksContainer.closest(`[data-type='${TYPES.COLUMN}']`); 91 | let columnId = Number(column.dataset.columnId); 92 | let taskId = Number(e.dataTransfer.getData("text/plain")); 93 | 94 | let tasks = column.querySelectorAll(".task"); 95 | let [closestBottomTask, closestIndex] = this.insertAboveTask( 96 | tasks, 97 | e.clientY 98 | ); 99 | 100 | let task = this.container.querySelector(".is-dragging"); 101 | 102 | if (!closestBottomTask) { 103 | tasksContainer.appendChild(task); 104 | updateTaskPosition(taskId, columnId, tasks.length); 105 | } else { 106 | tasksContainer.insertBefore(task, closestBottomTask); 107 | updateTaskPosition(taskId, columnId, closestIndex); 108 | } 109 | } 110 | 111 | insertAboveTask(tasks, mouseY) { 112 | let closestTask = null; 113 | let closestOffset = Number.NEGATIVE_INFINITY; 114 | let closestIndex = null; 115 | 116 | tasks.forEach((task, index) => { 117 | const { top } = task.getBoundingClientRect(); 118 | 119 | const offset = mouseY - top; 120 | 121 | if (offset < 0 && offset > closestOffset) { 122 | closestOffset = offset; 123 | closestTask = task; 124 | closestIndex = index; 125 | } 126 | }); 127 | 128 | return [closestTask, closestIndex]; 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /frontend-machine-coding/kanban-board/components/Kanban/kanban.styles.css: -------------------------------------------------------------------------------- 1 | .kanban-board { 2 | display: flex; 3 | gap: 1rem; 4 | padding: 1rem; 5 | position: relative; 6 | width: 100%; 7 | height: 100%; 8 | overflow-y: hidden; 9 | } 10 | 11 | .add-column { 12 | height: 2rem; 13 | width: 2rem; 14 | padding: 1.5rem; 15 | font-size: 2rem; 16 | display: flex; 17 | justify-content: center; 18 | align-items: center; 19 | border-radius: 0.5rem; 20 | border: none; 21 | cursor: pointer; 22 | } 23 | 24 | .is-dragging { 25 | scale: 1.05; 26 | background-color: rgb(50, 50, 50) !important; 27 | color: white; 28 | } 29 | -------------------------------------------------------------------------------- /frontend-machine-coding/kanban-board/components/Overlay/Overlay.js: -------------------------------------------------------------------------------- 1 | import { addColumn, addTask } from "../../services/API.js"; 2 | import { 3 | ACTIONS, 4 | ADD_COLUMN_EVENT, 5 | ADD_TASK_EVENT, 6 | getElementFromHtml, 7 | } from "../../utils.js"; 8 | import { Column } from "../Column/Column.js"; 9 | import { Task } from "../Task/Task.js"; 10 | 11 | export class Overlay { 12 | constructor(container, addColumnButton) { 13 | this.domElement = this.getElement(); 14 | this.inputElement = this.domElement.querySelector("input"); 15 | this.addButton = this.domElement.querySelector("button"); 16 | this.container = container; 17 | this.addColumnButton = addColumnButton; 18 | this.action = ""; 19 | this.columnId = ""; 20 | this.setupListeners(); 21 | } 22 | 23 | getElement() { 24 | return getElementFromHtml(` 25 |
26 | 27 | 28 |
29 | `); 30 | } 31 | 32 | setupListeners() { 33 | this.domElement.addEventListener(ADD_COLUMN_EVENT, (e) => { 34 | let { action } = e.detail; 35 | this.action = action; 36 | this.toggleOverlay(); 37 | }); 38 | 39 | this.domElement.addEventListener(ADD_TASK_EVENT, (e) => { 40 | let { action, columnId } = e.detail; 41 | this.action = action; 42 | this.columnId = columnId; 43 | this.toggleOverlay(); 44 | }); 45 | 46 | this.addButton.addEventListener("click", (e) => { 47 | if (this.action === ACTIONS.ADD_COLUMN) { 48 | this.addColumn(e); 49 | } else if (this.action === ACTIONS.ADD_TASK) { 50 | this.addTask(e); 51 | } 52 | }); 53 | } 54 | 55 | addTask(e) { 56 | let value = this.inputElement.value.trim(); 57 | let task = addTask(this.columnId, value); 58 | 59 | let taskEl = new Task(this.columnId, task.taskId, task.content).domElement; 60 | 61 | let selectedColumn = this.container.querySelector( 62 | `[data-column-id="${this.columnId}"]` 63 | ); 64 | 65 | selectedColumn.querySelector(".tasks").appendChild(taskEl); 66 | this.inputElement.value = ""; 67 | this.toggleOverlay(); 68 | } 69 | 70 | addColumn(e) { 71 | let value = this.inputElement.value.trim(); 72 | 73 | let newColumn = addColumn(value); 74 | let newColumnEl = new Column(newColumn.columnId, newColumn.title) 75 | .domElement; 76 | this.container.insertBefore(newColumnEl, this.addColumnButton); 77 | 78 | this.inputElement.value = ""; 79 | this.toggleOverlay(); 80 | } 81 | 82 | toggleOverlay() { 83 | this.domElement.classList.toggle("flex"); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /frontend-machine-coding/kanban-board/components/Overlay/overlay.styles.css: -------------------------------------------------------------------------------- 1 | .add-task-column-container { 2 | background-color: #f4f4f4; 3 | position: absolute; 4 | bottom: 2rem; 5 | left: 1rem; 6 | right: 1rem; 7 | height: 3rem; 8 | border-radius: 0.2rem; 9 | padding: 0.3rem; 10 | display: none; 11 | } 12 | 13 | .entity-input { 14 | flex: 1; 15 | border: none; 16 | border-radius: 0.2rem; 17 | padding: 0 1rem; 18 | } 19 | 20 | .add-task-column { 21 | width: 2rem; 22 | font-size: 2rem; 23 | border: none; 24 | cursor: pointer; 25 | } 26 | 27 | .flex { 28 | display: flex; 29 | } 30 | -------------------------------------------------------------------------------- /frontend-machine-coding/kanban-board/components/Task/Task.js: -------------------------------------------------------------------------------- 1 | import { ACTIONS, TYPES, getElementFromHtml } from "../../utils.js"; 2 | 3 | export class Task { 4 | constructor(columnId, taskId, content) { 5 | this.domElement = this.getElement(columnId, taskId, content); 6 | } 7 | 8 | getElement(columnId, taskId, content) { 9 | return getElementFromHtml(` 10 |
  • 11 |

    ${content}

    12 | 13 |
  • 14 | `); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /frontend-machine-coding/kanban-board/components/Task/task.styles.css: -------------------------------------------------------------------------------- 1 | .task { 2 | background-color: white; 3 | padding: 1rem; 4 | border-radius: 2%; 5 | box-shadow: 0px 5px 15px rgba(0, 0, 0, 0.25); 6 | display: flex; 7 | justify-content: space-between; 8 | align-items: center; 9 | gap: 0.5rem; 10 | cursor: move; 11 | width: 15rem; 12 | } 13 | 14 | .task-input { 15 | flex: 1; 16 | padding: 0.5rem; 17 | border-radius: 2%; 18 | border: none; 19 | background-color: rgb(239 239 239 / 60%); 20 | resize: none; 21 | width: 100%; 22 | overflow: auto; 23 | cursor: not-allowed; 24 | } 25 | 26 | .delete-task { 27 | background: none; 28 | border: none; 29 | cursor: pointer; 30 | } 31 | -------------------------------------------------------------------------------- /frontend-machine-coding/kanban-board/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Kanban Board 7 | 8 | 9 | 10 | 11 | 12 |
    13 | 14 | -------------------------------------------------------------------------------- /frontend-machine-coding/kanban-board/index.js: -------------------------------------------------------------------------------- 1 | import { Kanban } from "./components/Kanban/Kanban.js"; 2 | 3 | new Kanban({ 4 | container: ".kanban-board", 5 | }); 6 | -------------------------------------------------------------------------------- /frontend-machine-coding/kanban-board/services/API.js: -------------------------------------------------------------------------------- 1 | import { generateId } from "../utils.js"; 2 | import * as STORAGE from "./LocalStorageAPI.js"; 3 | 4 | let columns = []; 5 | 6 | export function getColumns() { 7 | columns = STORAGE.getColumns(); 8 | return columns; 9 | } 10 | 11 | export function saveColumns(columns) { 12 | STORAGE.saveColumns(columns); 13 | } 14 | 15 | export function addColumn(title) { 16 | let newColumn = { 17 | columnId: generateId(), 18 | title, 19 | tasks: [], 20 | }; 21 | columns.push(newColumn); 22 | saveColumns(columns); 23 | return newColumn; 24 | } 25 | 26 | export function deleteColumn(columnId) { 27 | columns = columns.filter((column) => column.columnId !== columnId); 28 | saveColumns(columns); 29 | } 30 | 31 | export function getTasks(columnId) { 32 | let columnData = columns.find((column) => column.columnId === columnId); 33 | 34 | if (!columnData) return []; 35 | 36 | return columnData.tasks; 37 | } 38 | 39 | export function addTask(columnId, content) { 40 | let newTask = { 41 | taskId: generateId(), 42 | content, 43 | }; 44 | 45 | columns = columns.map((column) => { 46 | if (column.columnId === columnId) { 47 | return { 48 | ...column, 49 | tasks: [...column.tasks, newTask], 50 | }; 51 | } 52 | return column; 53 | }); 54 | 55 | saveColumns(columns); 56 | return newTask; 57 | } 58 | 59 | export function deleteTask(columnId, taskId) { 60 | columns = columns.map((column) => { 61 | if (column.columnId === columnId) { 62 | return { 63 | ...column, 64 | tasks: column.tasks.filter((task) => task.taskId !== taskId), 65 | }; 66 | } 67 | return column; 68 | }); 69 | 70 | saveColumns(columns); 71 | } 72 | 73 | export function updateTaskPosition(taskId, newColumnId, newPosition) { 74 | let currentTask; 75 | let existingColumn; 76 | // Find task and remove it from its current Location 77 | for (const column of columns) { 78 | let task = column.tasks.find((task) => task.taskId === taskId); 79 | 80 | if (task) { 81 | existingColumn = column; 82 | currentTask = task; 83 | 84 | columns = columns.map((column) => { 85 | if (column.columnId === existingColumn.columnId) { 86 | return { 87 | ...column, 88 | tasks: column.tasks.filter( 89 | (task) => task.taskId !== currentTask.taskId 90 | ), 91 | }; 92 | } 93 | return column; 94 | }); 95 | 96 | break; 97 | } 98 | } 99 | 100 | // Next we need to add it to new Location 101 | 102 | columns = columns.map((column) => { 103 | if (column.columnId === newColumnId && currentTask) { 104 | column.tasks.splice(newPosition, 0, currentTask); 105 | return { 106 | ...column, 107 | tasks: column.tasks, 108 | }; 109 | } 110 | return column; 111 | }); 112 | 113 | saveColumns(columns); 114 | } 115 | -------------------------------------------------------------------------------- /frontend-machine-coding/kanban-board/services/LocalStorageAPI.js: -------------------------------------------------------------------------------- 1 | let KEY = "KANBAN-BOARD"; 2 | 3 | /** 4 | * @typedef Task 5 | * @type {object} 6 | * @property {number} taskId 7 | * @property {string} content 8 | */ 9 | 10 | /** 11 | * @typedef Column 12 | * @type {object} 13 | * @property {number} columnId 14 | * @property {string} title 15 | * @property {Task[]} tasks 16 | */ 17 | 18 | /** 19 | * 20 | * @returns {Column[]} 21 | */ 22 | export function getColumns() { 23 | let columns = localStorage.getItem(KEY) ?? "[]"; 24 | return JSON.parse(columns); 25 | } 26 | 27 | /** 28 | * 29 | * @param {Column[]} columns 30 | */ 31 | export function saveColumns(columns) { 32 | localStorage.setItem(KEY, JSON.stringify(columns)); 33 | } 34 | -------------------------------------------------------------------------------- /frontend-machine-coding/kanban-board/style.css: -------------------------------------------------------------------------------- 1 | @import url("./components/Kanban/kanban.styles.css"); 2 | @import url("./components/Column/column.styles.css"); 3 | @import url("./components/Task/task.styles.css"); 4 | @import url("./components/Overlay/overlay.styles.css"); 5 | 6 | *, 7 | *::before, 8 | *::after { 9 | margin: 0; 10 | padding: 0; 11 | box-sizing: border-box; 12 | font-family: sans-serif; 13 | } 14 | 15 | body { 16 | background-color: rgb(86, 86, 86); 17 | width: 100%; 18 | height: 100vh; 19 | user-select: none; 20 | } 21 | -------------------------------------------------------------------------------- /frontend-machine-coding/kanban-board/utils.js: -------------------------------------------------------------------------------- 1 | export const ACTIONS = { 2 | ADD_COLUMN: "ADD_COLUMN", 3 | DELETE_COLUMN: "DELETE_COLUMN", 4 | ADD_TASK: "ADD_TASK", 5 | DELETE_TASK: "DELETE_TASK", 6 | }; 7 | 8 | export const TYPES = { 9 | COLUMN: "COLUMN", 10 | TASK: "TASK", 11 | }; 12 | 13 | export const ADD_TASK_EVENT = "ADD_TASK_EVENT"; 14 | export const ADD_COLUMN_EVENT = "ADD_COLUMN_EVENT"; 15 | 16 | /** 17 | * 18 | * @param {string} html 19 | * @returns 20 | */ 21 | export function getElementFromHtml(html) { 22 | let children = document 23 | .createRange() 24 | .createContextualFragment(html.trim()).children; 25 | return children[0]; 26 | } 27 | 28 | /** 29 | * 30 | * @returns {number} 31 | */ 32 | export function generateId() { 33 | return Math.floor(Math.random() * 1000); 34 | } 35 | -------------------------------------------------------------------------------- /frontend-machine-coding/modal-component/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Modal Component 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /frontend-machine-coding/modal-component/scripts/index.js: -------------------------------------------------------------------------------- 1 | import { Modal } from "./lib.js"; 2 | 3 | new Modal({ 4 | container: ".modal-container", 5 | buttonSelector: ".openButton", 6 | title: "Title", 7 | content: `

    Modal content

    `, 8 | }); 9 | -------------------------------------------------------------------------------- /frontend-machine-coding/modal-component/scripts/lib.js: -------------------------------------------------------------------------------- 1 | let defaultConfig = { 2 | container: ".modal-container", 3 | buttonSelector: ".openButton", 4 | title: "title", 5 | content: `

    content

    `, 6 | }; 7 | 8 | export class Modal { 9 | constructor(config) { 10 | this.config = JSON.parse(JSON.stringify(defaultConfig)); 11 | this.init(config); 12 | } 13 | 14 | init(config) { 15 | Object.keys(config).forEach((key) => { 16 | this.config[key] = config[key]; 17 | }); 18 | this.createModal(); 19 | } 20 | 21 | createModal() { 22 | this.container = document.querySelector(this.config.container); 23 | this.openButton = document.querySelector(this.config.buttonSelector); 24 | 25 | if (!this.container) return; 26 | if (!this.openButton) return; 27 | 28 | this.overlayEl = document.createElement("div"); 29 | this.overlayEl.setAttribute("class", "overlay"); 30 | document.body.appendChild(this.overlayEl); 31 | 32 | let modalHeader = document.createElement("div"); 33 | modalHeader.innerHTML = ` 34 | 38 | `; 39 | 40 | let modalBody = document.createElement("div"); 41 | modalBody.innerHTML = this.config.content; 42 | 43 | let frag = document.createDocumentFragment(); 44 | 45 | frag.appendChild(modalHeader); 46 | frag.appendChild(modalBody); 47 | 48 | this.container.appendChild(frag); 49 | this.setupListeners(); 50 | } 51 | 52 | setupListeners() { 53 | this.openButton.addEventListener("click", () => { 54 | this.overlayEl.classList.add("active"); 55 | this.container.classList.add("active"); 56 | }); 57 | 58 | let closeBtn = this.container.querySelector("button"); 59 | closeBtn.addEventListener("click", () => { 60 | this.overlayEl.classList.remove("active"); 61 | this.container.classList.remove("active"); 62 | }); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /frontend-machine-coding/modal-component/style.css: -------------------------------------------------------------------------------- 1 | .overlay { 2 | position: fixed; 3 | top: 0; 4 | bottom: 0; 5 | right: 0; 6 | left: 0; 7 | opacity: 0; 8 | pointer-events: none; 9 | background-color: rgba(0, 0, 0, 0.5); 10 | } 11 | 12 | .overlay.active { 13 | opacity: 1; 14 | pointer-events: auto; 15 | } 16 | 17 | .modal-container { 18 | position: fixed; 19 | top: 50%; 20 | left: 50%; 21 | translate: -50% -50%; 22 | scale: 0; 23 | transition: 300ms ease; 24 | width: 70%; 25 | border: 1px solid black; 26 | z-index: 10; 27 | background-color: white; 28 | padding: 1rem; 29 | border-radius: 1rem; 30 | } 31 | 32 | .modal-container.active { 33 | scale: 1; 34 | } 35 | 36 | .modal-header { 37 | display: flex; 38 | gap: 1rem; 39 | justify-content: space-between; 40 | align-items: center; 41 | } 42 | 43 | .modal-header button { 44 | cursor: pointer; 45 | border: none; 46 | background: none; 47 | font-size: 1.25rem; 48 | } 49 | -------------------------------------------------------------------------------- /frontend-machine-coding/otp-input/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | OTP Input 7 | 8 | 9 | 10 | 11 |
    12 | 13 | -------------------------------------------------------------------------------- /frontend-machine-coding/otp-input/scripts/index.js: -------------------------------------------------------------------------------- 1 | import { OTPInput } from "./lib.js"; 2 | 3 | new OTPInput({ 4 | container: ".otp-input-container", 5 | count: 5, 6 | }); 7 | -------------------------------------------------------------------------------- /frontend-machine-coding/otp-input/scripts/lib.js: -------------------------------------------------------------------------------- 1 | let defaultConfig = { 2 | container: ".otp-input", 3 | count: 5, 4 | }; 5 | 6 | export class OTPInput { 7 | constructor(config) { 8 | this.config = JSON.parse(JSON.stringify(defaultConfig)); 9 | this.init(config); 10 | } 11 | 12 | init(config) { 13 | Object.keys(config).forEach((key) => { 14 | this.config[key] = config[key]; 15 | }); 16 | 17 | this.createOTP(); 18 | } 19 | 20 | createOTP() { 21 | this.container = document.querySelector(this.config.container); 22 | 23 | if (!this.container) return; 24 | 25 | let frag = document.createDocumentFragment(); 26 | 27 | for (let index = 0; index < this.config.count; index++) { 28 | let inputEl = document.createElement("input"); 29 | inputEl.classList.add("otp"); 30 | inputEl.setAttribute("type", "text"); 31 | inputEl.setAttribute("inputmode", "numeric"); 32 | inputEl.setAttribute("maxlength", "1"); 33 | 34 | frag.appendChild(inputEl); 35 | } 36 | 37 | this.container.appendChild(frag); 38 | this.container.addEventListener("input", this.onInput.bind(this)); 39 | this.container.addEventListener("keyup", this.onKeyUp.bind(this)); 40 | } 41 | 42 | onInput(e) { 43 | let target = e.target; 44 | let value = target.value; 45 | 46 | if (isNaN(value)) { 47 | target.value = ""; 48 | return; 49 | } 50 | 51 | if (value) { 52 | let nextElementSibling = target.nextElementSibling; 53 | if (nextElementSibling) { 54 | nextElementSibling.focus(); 55 | } 56 | } 57 | } 58 | onKeyUp(e) { 59 | let target = e.target; 60 | let key = e.key.toLowerCase(); 61 | 62 | if (["delete", "backspace"].includes(key)) { 63 | target.value = ""; 64 | let previousElementSibling = target.previousElementSibling; 65 | 66 | if (previousElementSibling) { 67 | previousElementSibling.focus(); 68 | } 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /frontend-machine-coding/otp-input/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | min-height: 100vh; 3 | display: flex; 4 | justify-content: center; 5 | align-items: center; 6 | background-color: #fbab7e; 7 | background-image: linear-gradient(62deg, #fbab7e 0%, #f7ce68 100%); 8 | } 9 | 10 | .otp-input-container { 11 | display: flex; 12 | overflow: auto; 13 | padding: 1rem; 14 | } 15 | 16 | .otp { 17 | border: none; 18 | width: 4rem; 19 | margin: 0 1rem; 20 | padding: 1rem; 21 | border-bottom: 2px solid rgba(0, 0, 0, 0.5); 22 | text-align: center; 23 | font-size: 2rem; 24 | pointer-events: none; 25 | } 26 | 27 | .otp:nth-child(1) { 28 | cursor: pointer; 29 | pointer-events: auto; 30 | } 31 | 32 | .otp:focus { 33 | outline: none; 34 | border-color: red; 35 | } 36 | -------------------------------------------------------------------------------- /frontend-machine-coding/star-rating/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Star Rating 7 | 8 | 9 | 10 | 11 |
    12 |
    13 | 14 | 15 | -------------------------------------------------------------------------------- /frontend-machine-coding/star-rating/scripts/index.js: -------------------------------------------------------------------------------- 1 | import { Rate } from "./lib.js"; 2 | 3 | new Rate({ 4 | container: ".star-rating-container", 5 | // count: 6, 6 | // onColor: "green", 7 | // offColor: "grey", 8 | // size: "10rem", 9 | }); 10 | -------------------------------------------------------------------------------- /frontend-machine-coding/star-rating/scripts/lib.js: -------------------------------------------------------------------------------- 1 | let starEntity = "★"; 2 | let defaultConfig = { 3 | container: "#stars", 4 | count: 5, 5 | onColor: "yellow", 6 | offColor: "white", 7 | size: "3rem", 8 | }; 9 | 10 | export class Rate { 11 | constructor(config) { 12 | this.config = JSON.parse(JSON.stringify(defaultConfig)); 13 | this.init(config); 14 | } 15 | 16 | init(config) { 17 | Object.keys(config).forEach((key) => { 18 | this.config[key] = config[key]; 19 | }); 20 | 21 | this.createStars(); 22 | } 23 | 24 | createStars() { 25 | this.container = document.querySelector(this.config.container); 26 | 27 | if (!this.container) return; 28 | 29 | let frag = document.createDocumentFragment(); 30 | 31 | for (let index = 0; index < this.config.count; index++) { 32 | let star = document.createElement("span"); 33 | star.innerHTML = starEntity; 34 | star.style.fontSize = this.config.size; 35 | star.style.color = this.config.offColor; 36 | star.style.cursor = "pointer"; 37 | star.dataset.rate = index + 1; 38 | 39 | frag.appendChild(star); 40 | } 41 | 42 | this.container.appendChild(frag); 43 | this.stars = this.container.children; 44 | this.filledStars = 0; 45 | 46 | this.container.addEventListener("click", this.onClick.bind(this)); 47 | this.container.addEventListener("mouseover", this.onMouseOver.bind(this)); 48 | this.container.addEventListener("mouseleave", this.onMouseLeave.bind(this)); 49 | } 50 | 51 | onClick(e) { 52 | let clickedStar = Number(e.target.dataset.rate); 53 | 54 | if (!clickedStar) return; 55 | 56 | for (let index = 0; index < this.filledStars; index++) { 57 | this.stars[index].style.color = this.config.offColor; 58 | } 59 | 60 | for (let index = 0; index < clickedStar; index++) { 61 | this.stars[index].style.color = this.config.onColor; 62 | } 63 | 64 | this.filledStars = clickedStar; 65 | } 66 | onMouseOver(e) { 67 | let hoveredStar = Number(e.target.dataset.rate); 68 | 69 | if (!hoveredStar) return; 70 | 71 | for (let index = 0; index < this.config.count; index++) { 72 | this.stars[index].style.color = this.config.offColor; 73 | } 74 | 75 | for (let index = 0; index < hoveredStar; index++) { 76 | this.stars[index].style.color = this.config.onColor; 77 | } 78 | } 79 | onMouseLeave() { 80 | for (let index = 0; index < this.config.count; index++) { 81 | this.stars[index].style.color = this.config.offColor; 82 | } 83 | 84 | for (let index = 0; index < this.filledStars; index++) { 85 | this.stars[index].style.color = this.config.onColor; 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /frontend-machine-coding/star-rating/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | min-height: 100vh; 3 | display: flex; 4 | justify-content: center; 5 | align-items: center; 6 | background-color: #00dbde; 7 | background-image: linear-gradient(90deg, #00dbde 0%, #fc00ff 100%); 8 | } 9 | -------------------------------------------------------------------------------- /frontend-machine-coding/style.css: -------------------------------------------------------------------------------- 1 | *, 2 | *::before, 3 | *::after { 4 | margin: 0; 5 | padding: 0; 6 | box-sizing: border-box; 7 | font-family: Georgia, "Times New Roman", Times, serif; 8 | } 9 | 10 | body { 11 | height: 100vh; 12 | width: 100%; 13 | height: 100vh; 14 | width: 100%; 15 | display: grid; 16 | grid-template-rows: auto 1fr; 17 | gap: 1rem; 18 | } 19 | 20 | h1 { 21 | padding: 1rem; 22 | width: 100%; 23 | background-color: black; 24 | color: white; 25 | text-align: center; 26 | } 27 | 28 | ul { 29 | list-style-type: none; 30 | margin: 0 auto; 31 | overflow: auto; 32 | padding: 1rem; 33 | } 34 | 35 | li::before { 36 | content: "👉 "; 37 | } 38 | 39 | a { 40 | text-decoration: none; 41 | font-size: 1.5rem; 42 | line-height: 1.5; 43 | color: black; 44 | } 45 | -------------------------------------------------------------------------------- /frontend-machine-coding/tabs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Tabs 7 | 8 | 9 | 10 | 11 |
    12 | 13 | -------------------------------------------------------------------------------- /frontend-machine-coding/tabs/scripts/index.js: -------------------------------------------------------------------------------- 1 | import { Tab } from "./lib.js"; 2 | 3 | new Tab({ 4 | container: ".tabs-container", 5 | data: [ 6 | { 7 | title: "home", 8 | content: ` 9 |

    Home page

    `, 10 | }, 11 | { 12 | title: "pricing", 13 | content: ` 14 |

    Pricing page

    `, 15 | }, 16 | { 17 | title: "checkout", 18 | content: ` 19 |

    Checkout page

    `, 20 | }, 21 | ], 22 | }); 23 | -------------------------------------------------------------------------------- /frontend-machine-coding/tabs/scripts/lib.js: -------------------------------------------------------------------------------- 1 | let defaultConfig = { 2 | container: ".tabs-container", 3 | data: [ 4 | { 5 | title: "title1", 6 | content: "content", 7 | }, 8 | ], 9 | }; 10 | 11 | export class Tab { 12 | constructor(config) { 13 | this.config = JSON.parse(JSON.stringify(config)); 14 | this.init(config); 15 | } 16 | 17 | init(config) { 18 | Object.keys(config).forEach((key) => { 19 | this.config[key] = config[key]; 20 | }); 21 | this.createTabs(); 22 | } 23 | 24 | createTabs() { 25 | this.container = document.querySelector(this.config.container); 26 | 27 | if (!this.container) return; 28 | 29 | this.tabsHeader = document.createElement("div"); 30 | this.tabsHeader.classList.add("tabs-header"); 31 | 32 | this.tabsContent = document.createElement("div"); 33 | this.tabsContent.classList.add("tabs-content"); 34 | 35 | let frag = document.createDocumentFragment(); 36 | let tabsHeaderFrag = document.createDocumentFragment(); 37 | let tabsContentFrag = document.createDocumentFragment(); 38 | 39 | for (let index = 0; index < this.config.data.length; index++) { 40 | let { title, content } = this.config.data[index]; 41 | 42 | let tabHeader = document.createElement("div"); 43 | tabHeader.setAttribute("data-tab-header", title); 44 | tabHeader.textContent = title.toUpperCase(); 45 | tabsHeaderFrag.appendChild(tabHeader); 46 | 47 | let tabContent = document.createElement("div"); 48 | tabContent.setAttribute("data-tab-content", title); 49 | tabContent.innerHTML = content; 50 | tabsContentFrag.appendChild(tabContent); 51 | } 52 | 53 | tabsHeaderFrag.childNodes[0].classList.add("active"); 54 | tabsContentFrag.childNodes[0].classList.add("active"); 55 | 56 | this.tabsHeader.appendChild(tabsHeaderFrag); 57 | this.tabsContent.appendChild(tabsContentFrag); 58 | 59 | frag.appendChild(this.tabsHeader); 60 | frag.appendChild(this.tabsContent); 61 | 62 | this.container.appendChild(frag); 63 | this.container.addEventListener("click", this.onClick.bind(this)); 64 | } 65 | 66 | onClick(e) { 67 | let tabValue = e.target.dataset.tabHeader; 68 | if (!tabValue) return; 69 | 70 | [...this.tabsHeader.children].forEach((tabHeader) => { 71 | tabHeader.classList.remove("active"); 72 | if (tabHeader.getAttribute("data-tab-header") === tabValue) { 73 | tabHeader.classList.add("active"); 74 | } 75 | }); 76 | 77 | [...this.tabsContent.children].forEach((tabContent) => { 78 | tabContent.classList.remove("active"); 79 | if (tabContent.getAttribute("data-tab-content") === tabValue) { 80 | tabContent.classList.add("active"); 81 | } 82 | }); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /frontend-machine-coding/tabs/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: Georgia, "Times New Roman", Times, serif; 3 | } 4 | 5 | [data-tab-content] { 6 | display: none; 7 | } 8 | 9 | .active[data-tab-content] { 10 | display: block; 11 | } 12 | 13 | .tabs-header { 14 | display: flex; 15 | gap: 1rem; 16 | border-bottom: 1px solid black; 17 | } 18 | 19 | .tabs-header > * { 20 | cursor: pointer; 21 | padding: 1rem; 22 | } 23 | 24 | .active[data-tab-header] { 25 | background-color: #2196f3; 26 | } 27 | 28 | [data-tab-header]:hover { 29 | background-color: #2196f3; 30 | } 31 | -------------------------------------------------------------------------------- /frontend-machine-coding/tic-tac-toe/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Star Rating 7 | 8 | 9 | 10 | 11 | 12 |
    13 |
    14 | 15 | -------------------------------------------------------------------------------- /frontend-machine-coding/tic-tac-toe/scripts/index.js: -------------------------------------------------------------------------------- 1 | import { TicTacToe } from "./lib.js"; 2 | 3 | new TicTacToe({ 4 | container: ".tic-tac-toe", 5 | // count: 5, 6 | cellStyle: "cell", 7 | styleX: "cellX", 8 | styleO: "cellO", 9 | resultContainer: ".result", 10 | }); 11 | -------------------------------------------------------------------------------- /frontend-machine-coding/tic-tac-toe/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | min-height: 100vh; 3 | display: flex; 4 | justify-content: center; 5 | align-items: center; 6 | flex-direction: column; 7 | font-family: sans-serif; 8 | gap: 1rem; 9 | background: #a8ff78; /* fallback for old browsers */ 10 | background: -webkit-linear-gradient( 11 | to right, 12 | #78ffd6, 13 | #a8ff78 14 | ); /* Chrome 10-25, Safari 5.1-6 */ 15 | background: linear-gradient( 16 | to right, 17 | #78ffd6, 18 | #a8ff78 19 | ); /* W3C, IE 10+/ Edge, Firefox 16+, Chrome 26+, Opera 12+, Safari 7+ */ 20 | } 21 | 22 | .cell { 23 | width: 5rem; 24 | height: 5rem; 25 | cursor: pointer; 26 | background-color: white; 27 | } 28 | 29 | .cellX { 30 | font-size: 3rem; 31 | color: orange; 32 | text-align: center; 33 | } 34 | 35 | .cellO { 36 | font-size: 3rem; 37 | color: orange; 38 | text-align: center; 39 | } 40 | -------------------------------------------------------------------------------- /frontend-machine-coding/todo-list/components/AddItem/AddItem.js: -------------------------------------------------------------------------------- 1 | import { getElementFromHtml } from "../../utils.js"; 2 | 3 | export class AddItem { 4 | constructor(onAddItemCallback) { 5 | this.domElement = this.getElement(); 6 | this.domElement.querySelector("button").addEventListener("click", (e) => { 7 | let inputEl = e.target.previousElementSibling; 8 | let value = inputEl.value; 9 | if (value) { 10 | onAddItemCallback(value); 11 | inputEl.value = ""; 12 | } else { 13 | alert("Please enter task"); 14 | } 15 | }); 16 | } 17 | 18 | getElement() { 19 | return getElementFromHtml(` 20 |
    21 | 22 | 23 |
    24 | `); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /frontend-machine-coding/todo-list/components/AddItem/add_item.style.css: -------------------------------------------------------------------------------- 1 | .add-item { 2 | display: flex; 3 | justify-content: space-between; 4 | align-items: center; 5 | background-color: #edeef0; 6 | border-radius: 1rem; 7 | } 8 | 9 | .add-item__input { 10 | border-radius: 0.2rem; 11 | border: none; 12 | border-radius: 1rem; 13 | flex: 1; 14 | background: none; 15 | outline: none; 16 | padding-left: 2%; 17 | } 18 | 19 | .add-item__button { 20 | cursor: pointer; 21 | background: #ff5945; 22 | border-radius: 1rem; 23 | border: none; 24 | padding: 0.5rem 1.5rem; 25 | color: white; 26 | font-weight: bold; 27 | } 28 | -------------------------------------------------------------------------------- /frontend-machine-coding/todo-list/components/Item/Item.js: -------------------------------------------------------------------------------- 1 | import { getElementFromHtml } from "../../utils.js"; 2 | 3 | export class Item { 4 | constructor(item) { 5 | this.domElement = this.getElement(item); 6 | this.checkbox = this.domElement.querySelector('[type="checkbox"]'); 7 | this.checkbox.checked = item.completed ? true : false; 8 | } 9 | 10 | getElement(item) { 11 | return getElementFromHtml(` 12 |
    13 | 14 |
    ${item.text}
    15 | 16 |
    17 | `); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /frontend-machine-coding/todo-list/components/Item/item.style.css: -------------------------------------------------------------------------------- 1 | .item { 2 | max-width: 30rem; 3 | margin: 0.5rem auto; 4 | display: flex; 5 | align-items: center; 6 | gap: 1rem; 7 | padding: 0.3rem 0; 8 | } 9 | 10 | .item__text { 11 | flex: 1; 12 | overflow: auto; 13 | } 14 | 15 | .item__delete { 16 | cursor: pointer; 17 | background: none; 18 | border: none; 19 | } 20 | 21 | .item__checkbox { 22 | border-radius: 100%; 23 | } 24 | 25 | .item__checkbox:checked ~ .item__text { 26 | text-decoration: line-through; 27 | } 28 | -------------------------------------------------------------------------------- /frontend-machine-coding/todo-list/components/ItemsContainer/ItemsContainer.js: -------------------------------------------------------------------------------- 1 | import { 2 | addItem, 3 | deleteItem, 4 | getItems, 5 | updateItem, 6 | } from "../../services/API.js"; 7 | import { getElementFromHtml } from "../../utils.js"; 8 | import { Item } from "../Item/Item.js"; 9 | 10 | export class ItemsContainer { 11 | constructor() { 12 | this.domElement = this.getElement(); 13 | this.init(); 14 | } 15 | 16 | getElement() { 17 | return getElementFromHtml(` 18 |
    19 | `); 20 | } 21 | 22 | async init() { 23 | this.setupListener(); 24 | this.items = await getItems(); 25 | this.renderItems(); 26 | } 27 | 28 | addNewItem(value) { 29 | let newItem = addItem(this.items, value); 30 | this.domElement.appendChild(new Item(newItem).domElement); 31 | } 32 | 33 | renderItems() { 34 | this.items.forEach((item) => { 35 | this.domElement.appendChild(new Item(item).domElement); 36 | }); 37 | } 38 | 39 | setupListener() { 40 | this.domElement.addEventListener("click", (e) => { 41 | let id = e.target.dataset.id; 42 | if (!id) return; 43 | 44 | if (e.target.classList.contains("item__checkbox")) { 45 | let checkedValue = e.target.checked; 46 | this.updateExistingElement(id, checkedValue); 47 | } else if (e.target.classList.contains("item__delete")) { 48 | this.deleteExistingElement(id); 49 | } 50 | }); 51 | } 52 | 53 | updateExistingElement(id, value) { 54 | updateItem(this.items, { 55 | id: Number(id), 56 | completed: value, 57 | }); 58 | } 59 | 60 | deleteExistingElement(id) { 61 | [...this.domElement.children].forEach((node) => { 62 | if (node.getAttribute("data-id") === id) { 63 | node.remove(); 64 | } 65 | }); 66 | deleteItem(this.items, Number(id)); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /frontend-machine-coding/todo-list/components/ItemsContainer/items_container.style.css: -------------------------------------------------------------------------------- 1 | .items-container { 2 | padding: 1rem; 3 | } 4 | -------------------------------------------------------------------------------- /frontend-machine-coding/todo-list/components/Todo/Todo.js: -------------------------------------------------------------------------------- 1 | import { AddItem } from "../AddItem/AddItem.js"; 2 | import { ItemsContainer } from "../ItemsContainer/ItemsContainer.js"; 3 | 4 | let defaultConfig = { 5 | container: ".todo-list", 6 | }; 7 | 8 | export class Todo { 9 | constructor(config) { 10 | this.config = JSON.parse(JSON.stringify(defaultConfig)); 11 | this.init(config); 12 | } 13 | 14 | init(config) { 15 | Object.keys(config).forEach((key) => { 16 | this.config[key] = config[key]; 17 | }); 18 | this.createTodo(); 19 | } 20 | 21 | createTodo() { 22 | this.container = document.querySelector(this.config.container); 23 | 24 | if (!this.container) return; 25 | 26 | this.addItemEl = new AddItem(this.addNewItem.bind(this)); 27 | this.container.appendChild(this.addItemEl.domElement); 28 | 29 | this.itemsContainer = new ItemsContainer(); 30 | this.container.appendChild(this.itemsContainer.domElement); 31 | } 32 | 33 | addNewItem(value) { 34 | this.itemsContainer.addNewItem(value); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /frontend-machine-coding/todo-list/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Todo List App 7 | 8 | 9 | 10 | 11 |
    12 |
    13 | 14 | -------------------------------------------------------------------------------- /frontend-machine-coding/todo-list/index.js: -------------------------------------------------------------------------------- 1 | import { Todo } from "./components/Todo/Todo.js"; 2 | 3 | new Todo({ 4 | container: ".todo-list", 5 | }); 6 | -------------------------------------------------------------------------------- /frontend-machine-coding/todo-list/services/API.js: -------------------------------------------------------------------------------- 1 | import * as STORAGE from "./LocalStorageAPI.js"; 2 | // import * as STORAGE from "./IndexedDbAPI.js"; 3 | 4 | /** 5 | * @typedef Item 6 | * @type {object} 7 | * @property {number} id 8 | * @property {boolean} completed 9 | * @property {string} text 10 | */ 11 | 12 | import { generateId } from "../utils.js"; 13 | 14 | export async function getItems() { 15 | return await STORAGE.getItems(); 16 | } 17 | 18 | /** 19 | * 20 | * @param {Item[]} items 21 | */ 22 | export function saveItems(items) { 23 | STORAGE.saveItems(items); 24 | } 25 | 26 | /** 27 | * 28 | * @param {Item[]} items 29 | * @param {string} text 30 | */ 31 | export function addItem(items, text) { 32 | let newItem = { 33 | id: generateId(), 34 | text, 35 | completed: false, 36 | }; 37 | 38 | items.push(newItem); 39 | saveItems(items); 40 | return newItem; 41 | } 42 | 43 | export function updateItem(items, updatedItem) { 44 | let oldItemIndex = items.findIndex((item) => item.id === updatedItem.id); 45 | let oldItem = items[oldItemIndex]; 46 | 47 | items.splice(oldItemIndex, 1, { 48 | ...oldItem, 49 | completed: updatedItem.completed, 50 | }); 51 | 52 | saveItems(items); 53 | } 54 | 55 | export function deleteItem(items, id) { 56 | let oldItemIndex = items.findIndex((item) => item.id === id); 57 | 58 | items.splice(oldItemIndex, 1); 59 | saveItems(items); 60 | } 61 | -------------------------------------------------------------------------------- /frontend-machine-coding/todo-list/services/IndexedDbAPI.js: -------------------------------------------------------------------------------- 1 | import { get, set } from "https://cdn.jsdelivr.net/npm/idb-keyval@6/+esm"; 2 | let KEY = "TODO-LIST"; 3 | 4 | export async function getItems() { 5 | let items = (await get(KEY)) ?? []; 6 | return items; 7 | } 8 | 9 | export function saveItems(items) { 10 | set(KEY, items); 11 | } 12 | -------------------------------------------------------------------------------- /frontend-machine-coding/todo-list/services/LocalStorageAPI.js: -------------------------------------------------------------------------------- 1 | let KEY = "TODO-LIST"; 2 | 3 | export function getItems() { 4 | let items = localStorage.getItem(KEY) ?? "[]"; 5 | return JSON.parse(items); 6 | } 7 | 8 | export function saveItems(items) { 9 | localStorage.setItem(KEY, JSON.stringify(items)); 10 | } 11 | -------------------------------------------------------------------------------- /frontend-machine-coding/todo-list/style.css: -------------------------------------------------------------------------------- 1 | @import url("./components/ItemsContainer/items_container.style.css"); 2 | @import url("./components/AddItem/add_item.style.css"); 3 | @import url("./components/Item/item.style.css"); 4 | 5 | *, 6 | *::before, 7 | *::after { 8 | margin: 0; 9 | padding: 0; 10 | box-sizing: border-box; 11 | font-family: Verdana, Geneva, Tahoma, sans-serif; 12 | user-select: none; 13 | } 14 | 15 | body { 16 | min-height: 100vh; 17 | width: 100%; 18 | padding: 1rem; 19 | background-color: mediumpurple; 20 | } 21 | 22 | .todo-list { 23 | background-color: white; 24 | padding: 1rem; 25 | border-radius: 1rem; 26 | max-width: 28rem; 27 | margin: 0 auto; 28 | } 29 | -------------------------------------------------------------------------------- /frontend-machine-coding/todo-list/utils.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * @param {string} html 4 | * @returns 5 | */ 6 | export function getElementFromHtml(html) { 7 | let children = document 8 | .createRange() 9 | .createContextualFragment(html.trim()).children; 10 | return children[0]; 11 | } 12 | 13 | /** 14 | * 15 | * @returns {number} 16 | */ 17 | export function generateId() { 18 | return Math.floor(Math.random() * 1000); 19 | } 20 | -------------------------------------------------------------------------------- /important-concepts/EventEmitter.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @class EventEmitter 3 | * A simple event emitter class that allows adding, removing, and emitting events. 4 | * It uses the observer design pattern to manage event listeners. 5 | * 6 | * @see '../design-patterns/observer-pattern' 7 | * @see '../design-patterns/pub-sub-pattern' 8 | */ 9 | class EventEmitter { 10 | constructor() { 11 | // A Map to store event names as keys and arrays of callback functions as values 12 | this.eventsMap = new Map(); 13 | } 14 | 15 | /** 16 | * @method addEventListener 17 | * Registers a callback function to be called when the specified event is emitted. 18 | * 19 | * @param {string} eventName - The name of the event to listen for. 20 | * @param {Function} cb - The callback function to be executed when the event is emitted. 21 | */ 22 | addEventListener(eventName, cb) { 23 | if (!this.eventsMap.has(eventName)) { 24 | // If the event doesn't exist, create a new entry with the callback 25 | this.eventsMap.set(eventName, [cb]); 26 | return; 27 | } 28 | 29 | // If the event already exists, add the new callback to the existing array 30 | const cbs = this.eventsMap.get(eventName); 31 | cbs.push(cb); 32 | this.eventsMap.set(eventName, cbs); 33 | } 34 | 35 | /** 36 | * @method removeEventListener 37 | * Removes a specific callback function from the specified event. 38 | * 39 | * @param {string} eventName - The name of the event to remove the listener from. 40 | * @param {Function} cb - The callback function to be removed. 41 | */ 42 | removeEventListener(eventName, cb) { 43 | if (!this.eventsMap.has(eventName)) { 44 | // If the event doesn't exist, there's nothing to remove 45 | return; 46 | } 47 | 48 | // Filter out the callback that needs to be removed 49 | let cbs = this.eventsMap.get(eventName); 50 | cbs = cbs.filter((currentCb) => currentCb !== cb); 51 | this.eventsMap.set(eventName, cbs); 52 | } 53 | 54 | /** 55 | * @method emitEvent 56 | * Emits an event, calling all registered callback functions associated with the event. 57 | * 58 | * @param {string} eventName - The name of the event to emit. 59 | * @param {...any} args - Any additional arguments to pass to the callback functions. 60 | */ 61 | emitEvent(eventName, ...args) { 62 | if (!this.eventsMap.has(eventName)) { 63 | // If the event doesn't exist, there's nothing to emit 64 | return; 65 | } 66 | 67 | // Get the array of callbacks associated with the event 68 | let cbs = this.eventsMap.get(eventName); 69 | cbs.forEach((cb) => { 70 | // Execute each callback using requestIdleCallback for non-blocking execution 71 | requestIdleCallback(() => cb(...args)); 72 | // Alternatively, you can use setTimeout to achieve similar behavior 73 | // setTimeout(() => cb(...args), 0); 74 | }); 75 | } 76 | } 77 | 78 | // Example usage: 79 | const emitter = new EventEmitter(); 80 | 81 | function greet(message) { 82 | console.log("Greet:", message); 83 | } 84 | 85 | // Add an event listener 86 | emitter.addEventListener("EVENT1", greet); 87 | 88 | // Emit the event, triggering the greet callback 89 | emitter.emitEvent("EVENT1", "Hello World!"); 90 | 91 | // Remove the event listener 92 | emitter.removeEventListener("EVENT1", greet); 93 | 94 | // Emit the event again, but the callback won't be triggered as it was removed 95 | emitter.emitEvent("EVENT1", "Hello World!"); 96 | 97 | // Output: 98 | // Greet: Hello World! 99 | -------------------------------------------------------------------------------- /important-concepts/auto-retry-fetch.js: -------------------------------------------------------------------------------- 1 | async function fetchWitAutoRetry(fetchData, retry) { 2 | return new Promise((resolve, reject) => { 3 | let attempts = 0; 4 | 5 | function attemptFetch() { 6 | fetchData() 7 | .then((res) => resolve(res)) 8 | .catch((err) => { 9 | attempts++; 10 | 11 | if (attempts < retry) { 12 | console.error(`Attempt ${attempts} failed. Retrying...`); 13 | attemptFetch(); // Retry the fetch 14 | } else { 15 | reject(err); 16 | } 17 | }); 18 | } 19 | 20 | attemptFetch(); 21 | }); 22 | } 23 | 24 | function fetchApi(successRate = 0.4) { 25 | return new Promise((resolve, reject) => { 26 | // Generate a random number between 0 and 1 27 | const random = Math.random(); 28 | 29 | // Determine whether to resolve or reject based on the successRate 30 | if (random < successRate) { 31 | resolve("Success!"); 32 | } else { 33 | reject(new Error("Failure!")); 34 | } 35 | }); 36 | } 37 | 38 | fetchWitAutoRetry(fetchApi, 3).then(console.log).catch(console.error); 39 | -------------------------------------------------------------------------------- /important-concepts/brute-force-currying.js: -------------------------------------------------------------------------------- 1 | const add = (a, b, c) => { 2 | return a + b + c; 3 | }; 4 | 5 | const addCurry = (a) => { 6 | return (b) => { 7 | return (c) => { 8 | return a + b + c; 9 | }; 10 | }; 11 | }; 12 | console.log(addCurry(2).toString()); 13 | console.log(addCurry(2)(3).toString()); 14 | console.log(addCurry(2)(3)(5)); 15 | -------------------------------------------------------------------------------- /important-concepts/classNames.js: -------------------------------------------------------------------------------- 1 | function classNames(...args) { 2 | const result = []; // Initialize an empty array to store class names 3 | 4 | // Iterate over each argument 5 | args.forEach((arg) => { 6 | // Skip falsy values (null, undefined, false, 0, etc.) 7 | if (!arg) { 8 | return; 9 | } 10 | 11 | // If the argument is a string or number, add it directly to the result 12 | if (typeof arg === "string" || typeof arg === "number") { 13 | result.push(arg); 14 | return; 15 | } 16 | 17 | // If the argument is an array, process each item 18 | if (Array.isArray(arg)) { 19 | for (const val of arg) { 20 | if (val) { 21 | // Recursively call classNames for nested arrays 22 | result.push(classNames(val)); 23 | } 24 | } 25 | return; 26 | } 27 | 28 | // If the argument is an object, process each key-value pair 29 | for (const key in arg) { 30 | const value = arg[key]; 31 | 32 | // If the value is truthy, add the key to the result 33 | if (value) { 34 | result.push(classNames(key)); 35 | } 36 | } 37 | }); 38 | 39 | // Join the array into a single string separated by spaces 40 | return result.join(" "); 41 | } 42 | 43 | // Test with strings and numbers 44 | console.log(classNames("btn", "btn-primary", 123)); 45 | // Output: "btn btn-primary 123" 46 | 47 | // Test with falsy values and nested arrays 48 | console.log(classNames("btn", null, "btn-primary", ["active", ["large"]])); 49 | // Output: "btn btn-primary active large" 50 | 51 | // Test with objects 52 | console.log(classNames("btn", { "btn-primary": true, "btn-secondary": false })); 53 | // Output: "btn btn-primary" 54 | 55 | // Test with nested arrays and objects 56 | console.log( 57 | classNames([ 58 | "btn", 59 | { "btn-primary": true, "btn-secondary": false }, 60 | ["large", { "text-center": true }], 61 | ]) 62 | ); 63 | // Output: "btn btn-primary large text-center" 64 | 65 | // Test with empty and mixed values 66 | console.log( 67 | classNames(null, undefined, "", false, 0, "class1", [ 68 | "class2", 69 | { class3: true }, 70 | ]) 71 | ); 72 | // Output: "class1 class2 class3" 73 | -------------------------------------------------------------------------------- /important-concepts/currying.js: -------------------------------------------------------------------------------- 1 | const curry = (fn) => { 2 | return function curried(...args) { 3 | if (args.length >= fn.length) { 4 | return fn(...args); 5 | } else { 6 | return curried.bind(null, ...args); 7 | } 8 | }; 9 | }; 10 | 11 | const add = (a, b, c) => { 12 | return a + b + c; 13 | }; 14 | 15 | const curriedAdd = curry(add); 16 | console.log(curriedAdd(1, 2, 3)); 17 | console.log(curriedAdd(1, 2)(3)); 18 | console.log(curriedAdd(1)(2, 3)); 19 | console.log(curriedAdd(1)(2)(3)); 20 | console.log(curriedAdd(1, 2)); 21 | console.log(curriedAdd(1)(2, 3, 4, 5)); 22 | -------------------------------------------------------------------------------- /important-concepts/debounce.js: -------------------------------------------------------------------------------- 1 | const debounce = (fn, delay) => { 2 | let timerId; 3 | return (...args) => { 4 | clearTimeout(timerId); 5 | 6 | timerId = setTimeout(() => { 7 | fn(...args); 8 | }, delay); 9 | }; 10 | }; 11 | 12 | let startTime = Date.now(); 13 | 14 | function fetchData() { 15 | console.log(`fetchData called after ${Date.now() - startTime}ms`); 16 | } 17 | 18 | // setTimeout(fetchData, 30); 19 | // setTimeout(fetchData, 40); 20 | 21 | const debouncedFn = debounce(fetchData, 100); 22 | 23 | setTimeout(debouncedFn, 30); 24 | setTimeout(debouncedFn, 40); 25 | -------------------------------------------------------------------------------- /important-concepts/flatten-1.js: -------------------------------------------------------------------------------- 1 | const arr = [1, [2], [3, [4]]]; 2 | // console.log(arr.flat(1)); 3 | // console.log(arr.flat(2)); 4 | 5 | // Recursive 6 | function flattenRecursive(arr, depth = 1) { 7 | if (depth === 0) { 8 | return arr; 9 | } 10 | 11 | const result = []; 12 | 13 | for (let i = 0; i < arr.length; i++) { 14 | const element = arr[i]; 15 | if (!Array.isArray(element)) { 16 | result.push(element); 17 | } else { 18 | const flattenedArr = flattenRecursive(element, depth - 1); 19 | result.push(...flattenedArr); 20 | } 21 | } 22 | 23 | return result; 24 | } 25 | 26 | // console.log(flattenRecursive(arr, 1)); 27 | // console.log(flattenRecursive(arr, 2)); 28 | 29 | // Recursive 30 | function flattenStack(arr, depth = 1) { 31 | const result = []; 32 | const stack = []; 33 | const newArr = arr.map((curr) => [curr, depth]); 34 | stack.push(...newArr); 35 | 36 | while (stack.length > 0) { 37 | const top = stack.pop(); 38 | const [curr, depth] = top; 39 | 40 | if (depth === 0) { 41 | result.push(curr); 42 | continue; 43 | } 44 | 45 | if (!Array.isArray(curr)) { 46 | result.push(curr); 47 | } else { 48 | const newArr = curr.map((curr) => [curr, depth - 1]); 49 | stack.push(...newArr); 50 | } 51 | } 52 | 53 | return result.reverse(); 54 | } 55 | 56 | console.log(flattenStack(arr, 1)); 57 | console.log(flattenStack(arr, 2)); 58 | -------------------------------------------------------------------------------- /important-concepts/flatten-2.js: -------------------------------------------------------------------------------- 1 | function flatten(input) { 2 | let result = {}; 3 | 4 | for (let key in input) { 5 | if (typeof input[key] === "object" && input[key] !== null) { 6 | result = { ...result, ...flatten(input[key]) }; 7 | } else { 8 | result[key] = input[key]; 9 | } 10 | } 11 | 12 | return result; 13 | } 14 | const obj1 = { 15 | a: 1, 16 | b: { 17 | c: 3, 18 | }, 19 | }; 20 | 21 | console.log(flatten(obj1)); 22 | -------------------------------------------------------------------------------- /important-concepts/immer.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Creates a deep clone of the base object, applies modifications through the recipe function, 3 | * and returns the clone if modifications are detected; otherwise, returns the original base object. 4 | * 5 | * @param {Object} base - The original object to be cloned. 6 | * @param {Function} recipe - A function that takes the clone and applies modifications to it. 7 | * @returns {Object} - The modified clone if changes are made, or the original base object if no changes. 8 | */ 9 | function produce(base, recipe) { 10 | // Create a deep clone of the base object 11 | const clone = structuredClone(base); 12 | 13 | // Apply modifications to the clone using the recipe function 14 | recipe(clone); 15 | 16 | // Check if the base and clone are identical by serializing them to JSON strings 17 | if (JSON.stringify(base) === JSON.stringify(clone)) { 18 | return base; // Return the base object if no changes were made 19 | } else { 20 | // Validate and adjust modifications if changes were detected 21 | validateModifications(base, clone); 22 | } 23 | 24 | return clone; // Return the modified clone 25 | } 26 | 27 | /** 28 | * Validates and adjusts the clone object based on modifications compared to the base object. 29 | * 30 | * @param {Object} base - The original object used for comparison. 31 | * @param {Object} clone - The modified object to be validated. 32 | * @returns {Object} - The adjusted clone or base object if no modifications are detected. 33 | */ 34 | function validateModifications(base, clone) { 35 | // If either base or clone is not an object, return the value if they are equal 36 | if (typeof base !== "object" || typeof clone !== "object") { 37 | return base === clone ? base : clone; 38 | } 39 | 40 | // If either base or clone is null, return the value if they are equal 41 | if (base === null || clone === null) { 42 | return base === clone ? base : clone; 43 | } 44 | 45 | // Get the keys of the clone object 46 | const keys = Object.keys(clone); 47 | 48 | // Iterate over each key in the clone object 49 | for (const key of keys) { 50 | // Compare each key's value in base and clone 51 | if (JSON.stringify(base[key]) === JSON.stringify(clone[key])) { 52 | // If values are the same, revert clone value to base value 53 | clone[key] = base[key]; 54 | } else { 55 | // Recursively validate and adjust nested objects 56 | validateModifications(base[key], clone[key]); 57 | } 58 | } 59 | } 60 | 61 | // 1. Basic Modification 62 | const base1 = { a: 1, b: 2 }; 63 | const modified1 = produce(base1, (obj) => { 64 | obj.b = 3; 65 | }); 66 | console.log(modified1); // Output: { a: 1, b: 3 } 67 | 68 | // 2. No Modification 69 | const base2 = { a: 1 }; 70 | const result2 = produce(base2, (obj) => { 71 | /* No modification */ 72 | }); 73 | console.log(result2); // Output: { a: 1 } 74 | 75 | // 3. Nested Object Modification 76 | const base3 = { a: 1, b: { c: 2, d: 3 } }; 77 | const modified3 = produce(base3, (obj) => { 78 | obj.b.c = 4; 79 | }); 80 | console.log(modified3); // Output: { a: 1, b: { c: 4, d: 3 } } 81 | 82 | // 4. Object with Array Modification 83 | const base4 = { a: [1, 2, 3] }; 84 | const modified4 = produce(base4, (obj) => { 85 | obj.a.push(4); 86 | }); 87 | console.log(modified4); // Output: { a: [1, 2, 3, 4] } 88 | -------------------------------------------------------------------------------- /important-concepts/isEqual.js: -------------------------------------------------------------------------------- 1 | function isEqual(a, b) { 2 | // Special case to handle NaN, as NaN !== NaN in JavaScript. 3 | if (Number.isNaN(a) && Number.isNaN(b)) { 4 | return true; 5 | } 6 | 7 | // If both values are strictly equal, return true (handles primitive types). 8 | if (a === b) { 9 | return true; 10 | } 11 | 12 | // If either value is not an object (or if they are different types), return false. 13 | if (typeof a !== "object" || typeof b !== "object") { 14 | return false; 15 | } 16 | 17 | // Get the keys of both objects for comparison. 18 | const keysA = Object.keys(a); 19 | const keysB = Object.keys(b); 20 | 21 | // If the objects have different numbers of keys, they are not equal. 22 | if (keysA.length !== keysB.length) { 23 | return false; 24 | } 25 | 26 | // Iterate over each key in the first object. 27 | for (let index = 0; index < keysA.length; index++) { 28 | const keyA = keysA[index]; 29 | const keyB = keysB[index]; 30 | 31 | // Recursively check if the keys and corresponding values are equal. 32 | if (!isEqual(a[keyA], b[keyB])) { 33 | return false; 34 | } 35 | } 36 | 37 | // If all checks pass, the objects are equal. 38 | return true; 39 | } 40 | 41 | console.log(isEqual(1, 1)); // true 42 | console.log(isEqual(1, 2)); // false 43 | console.log(isEqual("hello", "hello")); // true 44 | console.log(isEqual("hello", "world")); // false 45 | console.log(isEqual(true, true)); // true 46 | console.log(isEqual(true, false)); // false 47 | console.log(isEqual(null, null)); // true 48 | console.log(isEqual(undefined, undefined)); // true 49 | console.log(isEqual(null, undefined)); // false 50 | 51 | console.log(isEqual(NaN, NaN)); // true 52 | console.log(isEqual(NaN, 1)); // false 53 | console.log(isEqual(NaN, "NaN")); // false 54 | 55 | console.log(isEqual([1, 2, 3], [1, 2, 3])); // true 56 | console.log(isEqual([1, 2, 3], [3, 2, 1])); // false 57 | console.log(isEqual([1, [2, 3]], [1, [2, 3]])); // true 58 | console.log(isEqual([1, [2, 3]], [1, [3, 2]])); // false 59 | console.log(isEqual([], [])); // true 60 | console.log(isEqual([1], [])); // false 61 | 62 | console.log(isEqual({ a: 1, b: 2 }, { a: 1, b: 2 })); // true 63 | console.log(isEqual({ a: 1, b: 2 }, { a: 1, b: 3 })); // false 64 | console.log(isEqual({ a: 1, b: { c: 2 } }, { a: 1, b: { c: 2 } })); // true 65 | console.log(isEqual({ a: 1, b: { c: 2 } }, { a: 1, b: { c: 3 } })); // false 66 | console.log(isEqual({}, {})); // true 67 | 68 | console.log(isEqual([1, { a: 2 }], [1, { a: 2 }])); // true 69 | console.log(isEqual({ a: [1, 2], b: 3 }, { a: [1, 2], b: 3 })); // true 70 | console.log(isEqual({ a: [1, 2], b: 3 }, { a: [1, 2, 3], b: 3 })); // false 71 | console.log(isEqual([{ a: 1 }, { b: 2 }], [{ a: 1 }, { b: 2 }])); // true 72 | 73 | // TODO need to fix 74 | console.log(isEqual({ a: 1, b: 2 }, { b: 2, a: 1 })); // true 75 | -------------------------------------------------------------------------------- /important-concepts/memoize.js: -------------------------------------------------------------------------------- 1 | function memoize(fn, keyGen) { 2 | // If no custom key generator is provided, use a default function that joins arguments with an underscore 3 | if (!keyGen) { 4 | keyGen = (...args) => args.join("_"); 5 | } 6 | 7 | // Initialize the cache using a Map to store computed results 8 | let cache = new Map(); 9 | 10 | // Return a new function that wraps the original function 11 | return function (...args) { 12 | // Generate a unique key based on the provided arguments 13 | const key = keyGen(...args); 14 | 15 | // Check if the result is already cached 16 | if (cache.has(key)) { 17 | console.log("Cached"); 18 | return cache.get(key); // Return the cached result 19 | } else { 20 | // Compute the result, cache it, and then return it 21 | const result = fn.call(this, ...args); 22 | cache.set(key, result); 23 | return result; 24 | } 25 | }; 26 | } 27 | 28 | // Test Cases 29 | 30 | // Simple memoization of a function that adds two numbers 31 | const add = (a, b) => a + b; 32 | const memoizedAdd = memoize(add); 33 | 34 | console.log(memoizedAdd(1, 2)); // Output: 3 (calculated) 35 | console.log(memoizedAdd(1, 2)); // Output: 3 (cached) 36 | console.log("\n"); 37 | 38 | console.log(memoizedAdd(2, 3)); // Output: 5 (calculated) 39 | console.log(memoizedAdd(2, 3)); // Output: 5 (cached) 40 | console.log("\n"); 41 | 42 | // Test memoization with a function that has a custom key generator 43 | const multiply = (a, b) => a * b; 44 | const memoizedMultiply = memoize(multiply, (a, b) => `${a}x${b}`); 45 | 46 | console.log(memoizedMultiply(2, 3)); // Output: 6 (calculated) 47 | console.log(memoizedMultiply(2, 3)); // Output: 6 (cached) 48 | console.log("\n"); 49 | 50 | // Test with a function that depends on `this` 51 | function User(name) { 52 | this.name = name; 53 | } 54 | 55 | User.prototype.getName = memoize(function () { 56 | return this.name; 57 | }); 58 | 59 | const user = new User("Alice"); 60 | console.log(user.getName()); // Output: "Alice" (calculated) 61 | console.log(user.getName()); // Output: "Alice" (cached) 62 | 63 | // Output 64 | // 3 65 | // Cached 66 | // 3 67 | 68 | // 5 69 | // Cached 70 | // 5 71 | 72 | // 6 73 | // Cached 74 | // 6 75 | 76 | // Alice 77 | // Cached 78 | // Alice 79 | -------------------------------------------------------------------------------- /important-concepts/memoizeLast.js: -------------------------------------------------------------------------------- 1 | function memoizeLast(fn, isArgsEqual) { 2 | // If no custom equality function is provided, use a default one 3 | if (!isArgsEqual) { 4 | isArgsEqual = (args1, args2) => { 5 | // Check if the argument arrays have different lengths 6 | if (args1.length !== args2.length) { 7 | return false; 8 | } else { 9 | // Compare each argument in the arrays 10 | for (let index = 0; index < args1.length; index++) { 11 | const value1 = args1[index]; 12 | const value2 = args2[index]; 13 | 14 | // If any argument differs, return false 15 | if (value1 !== value2) { 16 | return false; 17 | } 18 | } 19 | } 20 | 21 | // If all arguments match, return true 22 | return true; 23 | }; 24 | } 25 | 26 | let lastArgs = []; // Stores the arguments from the last function call 27 | let lastResult; // Stores the result of the last function call 28 | 29 | return function (...args) { 30 | // If the current arguments match the previous ones, return the cached result 31 | if (isArgsEqual(args, lastArgs)) { 32 | console.log("Cached result used"); 33 | return lastResult; 34 | } else { 35 | // Otherwise, compute the result, cache it, and update the lastArgs 36 | const result = fn.call(this, ...args); 37 | lastArgs = args; 38 | lastResult = result; 39 | return result; 40 | } 41 | }; 42 | } 43 | 44 | // Test Cases 45 | 46 | // Simple memoization of a function that adds two numbers 47 | const add = (a, b) => a + b; 48 | const memoizedAdd = memoizeLast(add); 49 | 50 | console.log(memoizedAdd(1, 2)); // Output: 3 (calculated) 51 | console.log(memoizedAdd(1, 2)); // Output: 3 (cached) 52 | console.log("\n"); 53 | 54 | console.log(memoizedAdd(2, 3)); // Output: 5 (calculated) 55 | console.log(memoizedAdd(2, 3)); // Output: 5 (cached) 56 | console.log("\n"); 57 | 58 | console.log(memoizedAdd(1, 2)); // Output: 3 (cached again) 59 | console.log("\n"); 60 | 61 | // Test memoization with a function that multiplies numbers 62 | const multiply = (a, b) => a * b; 63 | const memoizedMultiply = memoizeLast(multiply); 64 | 65 | console.log(memoizedMultiply(3, 4)); // Output: 12 (calculated) 66 | console.log(memoizedMultiply(3, 4)); // Output: 12 (cached) 67 | console.log("\n"); 68 | 69 | console.log(memoizedMultiply(2, 5)); // Output: 10 (calculated) 70 | console.log(memoizedMultiply(2, 5)); // Output: 10 (cached) 71 | console.log("\n"); 72 | 73 | // Test with a custom equality checker (ignoring the order of arguments) 74 | const customComparator = (args1, args2) => { 75 | // Sort the arguments before comparing 76 | return args1.sort().toString() === args2.sort().toString(); 77 | }; 78 | const memoizedSumUnordered = memoizeLast(add, customComparator); 79 | 80 | console.log(memoizedSumUnordered(2, 1)); // Output: 3 (calculated) 81 | console.log(memoizedSumUnordered(1, 2)); // Output: 3 (cached, even though the order changed) 82 | 83 | // Output 84 | // 3 85 | // Cached result used 86 | // 3 87 | 88 | // 5 89 | // Cached result used 90 | // 5 91 | 92 | // 3 93 | 94 | // 12 95 | // Cached result used 96 | // 12 97 | 98 | // 10 99 | // Cached result used 100 | // 10 101 | 102 | // 3 103 | // Cached result used 104 | // 3 105 | -------------------------------------------------------------------------------- /important-concepts/negative-indexing.js: -------------------------------------------------------------------------------- 1 | function wrap(arr) { 2 | return new Proxy(arr, { 3 | get(target, property) { 4 | let index = Number(property); 5 | 6 | if (index < 0) { 7 | index += target.length; 8 | // TODO use refelect later 9 | return target[index]; 10 | } 11 | return target[index]; 12 | }, 13 | set(target, property, value) { 14 | let index = Number(property); 15 | 16 | if (index < 0) { 17 | index += target.length; 18 | 19 | if (index < 0) { 20 | throw new Error("Index out of bounds"); 21 | } 22 | 23 | target[index] = value; 24 | 25 | return true; 26 | } 27 | 28 | target[index] = value; 29 | return true; 30 | }, 31 | }); 32 | } 33 | 34 | const letters = ["a", "b", "c"]; 35 | const wrappedLetters = wrap(letters); 36 | 37 | console.log(wrappedLetters[0]); 38 | console.log(wrappedLetters[-1]); 39 | -------------------------------------------------------------------------------- /important-concepts/pipe.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * @param {string} input 4 | * @param {number} start 5 | * @param {number} end 6 | * @returns {string} 7 | */ 8 | function getPart(input, start, end) { 9 | return input.substring(start, end); 10 | } 11 | 12 | /** 13 | * 14 | * @param {string} input 15 | * @returns {string} 16 | */ 17 | function getUpperCasePart(input) { 18 | return input.toUpperCase(); 19 | } 20 | 21 | /** 22 | * 23 | * @param {string} input 24 | * @returns {Promise} 25 | */ 26 | async function getReversedPartAsync(input) { 27 | return new Promise((res) => { 28 | setTimeout(() => { 29 | let reversedString = input.split("").reverse().join(""); 30 | res(reversedString); 31 | }, 2000); 32 | }); 33 | } 34 | 35 | console.group("Without pipe sync example"); 36 | let res1 = getPart("Hello world", 3, 6); 37 | let res2 = getUpperCasePart(res1); 38 | console.log({ res1 }); 39 | console.log({ res2 }); 40 | console.groupEnd("Without pipe sync example"); 41 | 42 | /* 43 | Without pipe sync example 44 | { res1: 'lo ' } 45 | { res2: 'LO ' } 46 | */ 47 | 48 | console.group("Without pipe async example"); 49 | let res3 = getPart("Hello world", 3, 6); 50 | let res4 = getUpperCasePart(res3); 51 | getReversedPartAsync(res4).then((res5) => { 52 | console.group("Without pipe async example"); 53 | console.log({ res5 }); 54 | console.groupEnd("Without pipe async example"); 55 | }); 56 | console.log({ res3 }); 57 | console.log({ res4 }); 58 | console.groupEnd("Without pipe async example"); 59 | 60 | /* 61 | Without pipe async example 62 | { res3: 'lo ' } 63 | { res4: 'LO ' } 64 | { res5: ' OL' } 65 | */ 66 | 67 | // 1. Using for loop 68 | 69 | function pipe(...fns) { 70 | return function (...initialArgs) { 71 | let result = initialArgs; 72 | 73 | fns.forEach((fn, index) => { 74 | // Spread the original arguments only for the first function 75 | result = index === 0 ? fn(...result) : fn(result); 76 | }); 77 | 78 | return result; 79 | }; 80 | } 81 | 82 | function pipeAsync(...fns) { 83 | return async function (...initialArgs) { 84 | let result = initialArgs; 85 | 86 | for (let index = 0; index < fns.length; index++) { 87 | const fn = fns[index]; 88 | 89 | result = index === 0 ? await fn(...result) : await fn(result); 90 | } 91 | 92 | return result; 93 | }; 94 | } 95 | 96 | console.group("With pipe for loop sync example"); 97 | let pipeSync1 = pipe(getPart, getUpperCasePart); 98 | console.log(pipeSync1("Hello world", 3, 6)); 99 | console.groupEnd("With pipe for loop sync example"); 100 | 101 | /* 102 | With pipe for loop sync example 103 | LO 104 | */ 105 | 106 | let pipeAsync1 = pipe(getPart, getUpperCasePart, getReversedPartAsync); 107 | pipeAsync1("Hello world", 3, 6).then((res) => { 108 | console.group("With pipe for loop async example"); 109 | console.log(res); 110 | console.groupEnd("With pipe for loop async example"); 111 | }); 112 | 113 | /* 114 | With pipe for loop async example 115 | OL 116 | */ 117 | 118 | // 2. Using reduce 119 | 120 | function pipe2(...fns) { 121 | return function (...initialArgs) { 122 | let result = fns.reduce((acc, fn, index) => { 123 | return index === 0 ? fn(...acc) : fn(acc); 124 | }, initialArgs); 125 | return result; 126 | }; 127 | } 128 | 129 | // TODO need to check async logic once more 130 | function pipeAsync2(...fns) { 131 | return function (...initialArgs) { 132 | let result = fns.reduce((acc, fn, index) => { 133 | return acc.then((res) => { 134 | return index === 0 ? fn(...res) : fn(res); 135 | }); 136 | }, Promise.resolve(initialArgs)); 137 | return result; 138 | }; 139 | } 140 | 141 | console.group("With pipe reduce sync example"); 142 | let pipeSync2 = pipe2(getPart, getUpperCasePart); 143 | console.log(pipeSync2("Hello world", 3, 6)); 144 | console.groupEnd("With pipe reduce sync example"); 145 | 146 | /* 147 | With pipe reduce sync example 148 | LO 149 | */ 150 | 151 | let async2 = pipeAsync2(getPart, getUpperCasePart, getReversedPartAsync); 152 | async2("Hello world", 3, 6).then((res) => { 153 | console.group("With pipe reduce async example"); 154 | console.log(res); 155 | console.groupEnd("With pipe reduce async example"); 156 | }); 157 | 158 | /* 159 | With pipe reduce async example 160 | OL 161 | */ 162 | -------------------------------------------------------------------------------- /important-concepts/throttling.js: -------------------------------------------------------------------------------- 1 | const throttle = (fn, delay) => { 2 | let timerFlag = null; 3 | 4 | return (...args) => { 5 | if (timerFlag === null) { 6 | fn(...args); 7 | 8 | timerFlag = setTimeout(() => { 9 | timerFlag = null; 10 | }, delay); 11 | } 12 | }; 13 | }; 14 | 15 | let prev = Date.now(); 16 | 17 | function fetchData() { 18 | console.log(`fetchData called after ${Date.now() - prev}ms`); 19 | prev = Date.now(); 20 | } 21 | 22 | // setInterval(fetchData, 10); 23 | 24 | const throttledFn = throttle(fetchData, 1000); 25 | 26 | setInterval(throttledFn, 10); 27 | -------------------------------------------------------------------------------- /important-concepts/typeof.js: -------------------------------------------------------------------------------- 1 | // IMPORTANT The main concept to solve the below problem is that each object in Javascript has an internal property called [[Class]] 2 | // which we can access using Object.prototype.toString 3 | 4 | /** 5 | * Retrieves the type of a given data value in a human-readable format. 6 | * 7 | * @param {unknown} data - The value whose type is to be determined. 8 | * @returns {string} - The type of the data value, in lowercase (e.g., "object", "array"). 9 | */ 10 | function getTypeOf(data) { 11 | // Use Object.prototype.toString to get a detailed string representation of the data type. 12 | const type = Object.prototype.toString.call(data); // [object type] 13 | 14 | // Extract the type name from the string (e.g., "[object Array]" becomes "Array"). 15 | // Remove the surrounding "[object " and "]" and convert to lowercase. 16 | return type.slice(1, -1).split(" ")[1].toLowerCase(); 17 | } 18 | 19 | // Using Object.prototype.toString to determine the type of a value is preferred over using toString directly due to its more precise and reliable type detection. Here’s why: 20 | 21 | const arr = [1, 2, 3]; 22 | const obj = { key: "value" }; 23 | 24 | console.log(arr.toString()); // Expected: '1,2,3' (String representation, not type) 25 | console.log(obj.toString()); // Expected: '[object Object]' (Not specific to the type) 26 | // Since each derived object overrides toString method. 27 | 28 | console.log(Object.prototype.toString.call(arr)); // Expected: '[object Array]' 29 | console.log(Object.prototype.toString.call(obj)); // Expected: '[object Object]' 30 | 31 | console.log("\n Test Cases: \n"); 32 | 33 | // Array 34 | const arr2 = [1, 2, 3]; 35 | console.log(getTypeOf(arr)); // Expected: 'array' 36 | 37 | // Plain Object 38 | const obj2 = { key: "value" }; 39 | console.log(getTypeOf(obj)); // Expected: 'object' 40 | 41 | // Date 42 | const date = new Date(); 43 | console.log(getTypeOf(date)); // Expected: 'date' 44 | 45 | // Regular Expression 46 | const regex = /abc/; 47 | console.log(getTypeOf(regex)); // Expected: 'regexp' 48 | 49 | // Function 50 | const func = function () {}; 51 | console.log(getTypeOf(func)); // Expected: 'function' 52 | 53 | // Null (typeof null is 'object', which is misleading) 54 | const nullValue = null; 55 | console.log(getTypeOf(nullValue)); // Expected: 'null' 56 | 57 | // Undefined 58 | const undefinedValue = undefined; 59 | console.log(getTypeOf(undefinedValue)); // Expected: 'undefined' 60 | 61 | // BigInt 62 | const bigIntValue = BigInt(12345678901234567890); 63 | console.log(getTypeOf(bigIntValue)); // Expected: 'bigint' 64 | 65 | // Symbol 66 | const symbolValue = Symbol("description"); 67 | console.log(getTypeOf(symbolValue)); // Expected: 'symbol' 68 | 69 | // Map 70 | const map = new Map(); 71 | map.set("key1", "value1"); 72 | map.set("key2", "value2"); 73 | console.log(getTypeOf(map)); // Expected: 'map' 74 | 75 | // Set 76 | const set = new Set(); 77 | set.add(1); 78 | set.add(2); 79 | console.log(getTypeOf(set)); // Expected: 'set' 80 | 81 | // Primitive Types 82 | console.log(getTypeOf(42)); // Expected: 'number' 83 | console.log(getTypeOf("Hello, World!")); // Expected: 'string' 84 | console.log(getTypeOf(true)); // Expected: 'boolean' 85 | -------------------------------------------------------------------------------- /important-concepts/virtual-dom-deserialization-II/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Document 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /important-concepts/virtual-dom-deserialization-II/index.js: -------------------------------------------------------------------------------- 1 | import virtualTree from "./virtualTree.js"; 2 | 3 | /** 4 | * Renders a virtual DOM representation into actual DOM elements. 5 | * 6 | * @param {Object} vDOM - The virtual DOM object to render. 7 | * @returns {HTMLElement} - The rendered DOM element. 8 | */ 9 | function render(vDOM) { 10 | const { type, props } = vDOM; 11 | 12 | // If vDOM is not an object, it is a text node. 13 | if (typeof vDOM !== "object") { 14 | // Create a text node with the vDOM value. 15 | const element = document.createTextNode(vDOM); 16 | return element; 17 | } 18 | 19 | // Create an HTML element of the type specified in vDOM. 20 | /** 21 | * @type {HTMLElement} 22 | */ 23 | const root = document.createElement(type); 24 | 25 | // Extract children and other attributes from props. 26 | const { children, ...attrProps } = props; 27 | 28 | // Set attributes on the created element. 29 | for (const attribute in attrProps) { 30 | const value = attrProps[attribute]; 31 | root.setAttribute(attribute, value); 32 | } 33 | 34 | // Render and append children elements if children is an array. 35 | if (Array.isArray(children)) { 36 | for (const child of children) { 37 | const element = render(child); 38 | root.appendChild(element); 39 | } 40 | } else { 41 | // Render and append a single child element if children is not an array. 42 | const element = render(children); 43 | root.appendChild(element); 44 | } 45 | 46 | return root; 47 | } 48 | 49 | // Render the virtual tree and append it to the document body. 50 | document.body.appendChild(render(virtualTree)); 51 | -------------------------------------------------------------------------------- /important-concepts/virtual-dom-deserialization-II/virtualTree.js: -------------------------------------------------------------------------------- 1 | export default { 2 | type: "div", 3 | props: { 4 | id: "root", 5 | className: "container", 6 | children: [ 7 | { 8 | type: "div", 9 | props: { 10 | className: "header", 11 | children: { 12 | type: "h1", 13 | props: { 14 | children: "Welcome to My Web Page", 15 | }, 16 | }, 17 | }, 18 | }, 19 | { 20 | type: "div", 21 | props: { 22 | className: "content", 23 | children: [ 24 | { 25 | type: "p", 26 | props: { 27 | children: 28 | "This is a simple example of HTML structure using various tags.", 29 | }, 30 | }, 31 | { 32 | type: "a", 33 | props: { 34 | href: "https://www.example.com", 35 | className: "link", 36 | target: "_blank", 37 | children: "Visit Example.com", 38 | }, 39 | }, 40 | ], 41 | }, 42 | }, 43 | { 44 | type: "div", 45 | props: { 46 | className: "content", 47 | children: { 48 | type: "button", 49 | props: { 50 | className: "button", 51 | children: "Click Me", 52 | }, 53 | }, 54 | }, 55 | }, 56 | { 57 | type: "div", 58 | props: { 59 | className: "content", 60 | children: { 61 | type: "span", 62 | props: { 63 | className: "highlight", 64 | children: "This is a highlighted span element.", 65 | }, 66 | }, 67 | }, 68 | }, 69 | ], 70 | }, 71 | }; 72 | -------------------------------------------------------------------------------- /important-concepts/virtual-dom-serialization-I/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Document 7 | 8 | 9 | 10 |
    11 |
    12 |

    Welcome to My Web Page

    13 |
    14 | 15 |
    16 |

    This is a simple example of HTML structure using various tags.

    17 | 18 | Visit Example.com 21 |
    22 | 23 |
    24 | 25 |
    26 | 27 |
    28 | This is a highlighted span element. 29 |
    30 |
    31 | 32 | 33 | -------------------------------------------------------------------------------- /polyfills/_/_.once.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Creates a function that can be called only once. Subsequent calls return the result of the first call. 3 | * 4 | * @param {Function} fn - The function to be called only once. 5 | * @returns {Function} A new function that wraps the original function `fn`. 6 | */ 7 | function once(fn) { 8 | // Flag to track if the function has already been called 9 | let isCalledOnce = false; 10 | 11 | // Variable to store the result of the function call 12 | let result; 13 | 14 | return function (...args) { 15 | // Check if the function has not been called yet 16 | if (!isCalledOnce) { 17 | // Call the function with the provided arguments and context 18 | result = fn.call(this, ...args); 19 | 20 | // Set the flag to true to prevent further calls 21 | isCalledOnce = true; 22 | } 23 | 24 | // Return the result of the first call 25 | return result; 26 | }; 27 | } 28 | 29 | function sayHello(name) { 30 | return `Hello, ${name}!`; 31 | } 32 | const sayHelloOnce = once(sayHello); 33 | 34 | console.log(sayHelloOnce("Alice")); // Output: "Hello, Alice!" (first call) 35 | console.log(sayHelloOnce("Bob")); // Output: "Hello, Alice!" (subsequent calls) 36 | -------------------------------------------------------------------------------- /polyfills/array/at.js: -------------------------------------------------------------------------------- 1 | const array1 = [5, 12, 8, 130, 44]; 2 | 3 | // console.log(array1.at(0)); 4 | // console.log(array1.at(-1)); 5 | 6 | // Array.prototype.pat = function (index) { 7 | // if (index < -this.length || index >= this.length) { 8 | // return undefined; 9 | // } 10 | 11 | // if (index >= 0) { 12 | // return this[index]; 13 | // } else if (index < 0) { 14 | // return this[this.length + index]; 15 | // } 16 | // }; 17 | 18 | // console.log(array1.pat(0)); 19 | // console.log(array1.pat(-1)); 20 | 21 | function at(array, index) { 22 | if (index < -array.length || index >= array.length) { 23 | return undefined; 24 | } 25 | 26 | if (index >= 0) { 27 | return array[index]; 28 | } else if (index < 0) { 29 | return array[array.length + index]; 30 | } 31 | } 32 | 33 | console.log(at(array1, 0)); 34 | console.log(at(array1, -1)); 35 | -------------------------------------------------------------------------------- /polyfills/array/concat.js: -------------------------------------------------------------------------------- 1 | const array1 = ["a", "b", "c"]; 2 | // const array2 = array1.concat(["d"], "G"); 3 | // console.log({ array1, array2 }); 4 | 5 | function concat(array, ...values) { 6 | const result = [...array]; 7 | 8 | for (let index = 0; index < values.length; index++) { 9 | const value = values[index]; 10 | if (Array.isArray(value)) { 11 | for (let index = 0; index < value.length; index++) { 12 | result.push(value[index]); 13 | } 14 | } else { 15 | result.push(value); 16 | } 17 | } 18 | 19 | return result; 20 | } 21 | 22 | const array2 = concat(array1, ["d"], "G"); 23 | console.log({ array1, array2 }); 24 | -------------------------------------------------------------------------------- /polyfills/array/entries.js: -------------------------------------------------------------------------------- 1 | const array1 = ["a", "b", "c"]; 2 | 3 | // const iterator = array1.entries(); 4 | // for (const key of iterator) { 5 | // console.log(key); 6 | // } 7 | 8 | function entries(array) { 9 | function* generator() { 10 | for (let index = 0; index < array.length; index++) { 11 | yield [index, array[index]]; 12 | } 13 | } 14 | return generator(); 15 | } 16 | 17 | const iterator = entries(array1); 18 | for (const key of iterator) { 19 | console.log(key); 20 | } 21 | -------------------------------------------------------------------------------- /polyfills/array/every.js: -------------------------------------------------------------------------------- 1 | const array1 = [1, 30, 39, 29, 10, 13]; 2 | 3 | const isBelowThreshold = (currentValue) => currentValue > 40; 4 | // console.log(array1.every(isBelowThreshold)); 5 | 6 | function every(array, callbackFn) { 7 | for (let index = 0; index < array.length; index++) { 8 | if (!callbackFn(array[index], index, array)) { 9 | return false; 10 | } 11 | } 12 | return true; 13 | } 14 | 15 | console.log(every(array1, isBelowThreshold)); 16 | -------------------------------------------------------------------------------- /polyfills/array/fill.js: -------------------------------------------------------------------------------- 1 | const array1 = [1, 2, 3, 4]; 2 | 3 | // console.log(array1.fill(0, 2, 4)); // [1, 2, 0, 0] 4 | 5 | // console.log(array1.fill(5, 1)); // [1, 5, 5, 5] 6 | 7 | // console.log(array1.fill(6)); // [6, 6, 6, 6] 8 | 9 | function fill(array, value, startIndex = 0, endIndex = array.length) { 10 | for (let index = startIndex; index < endIndex; index++) { 11 | array[index] = value; 12 | } 13 | return array; 14 | } 15 | 16 | console.log(fill(array1, 0, 2, 4)); // [1, 2, 0, 0] 17 | 18 | console.log(fill(array1, 5, 1)); // [1, 5, 5, 5] 19 | 20 | console.log(fill(array1, 6)); // [6, 6, 6, 6] 21 | -------------------------------------------------------------------------------- /polyfills/array/filter.js: -------------------------------------------------------------------------------- 1 | const words = [ 2 | "spray", 3 | "limit", 4 | "elite", 5 | "exuberant", 6 | "destruction", 7 | "present", 8 | ]; 9 | 10 | // const result = words.filter((word) => word.length > 6); 11 | 12 | // console.log(result); 13 | 14 | function filter(array, callbackFn) { 15 | let result = []; 16 | for (let index = 0; index < array.length; index++) { 17 | const value = array[index]; 18 | if (callbackFn(value, index, array)) { 19 | result.push(value); 20 | } 21 | } 22 | return result; 23 | } 24 | 25 | const result = filter(words, (word) => word.length > 6); 26 | console.log(result); 27 | -------------------------------------------------------------------------------- /polyfills/array/find.js: -------------------------------------------------------------------------------- 1 | const array1 = [5, 12, 8, 130, 44]; 2 | 3 | // const found = array1.find((element) => element > 10); 4 | 5 | // console.log(found); 6 | 7 | function find(array, callbackFn) { 8 | for (let index = 0; index < array.length; index++) { 9 | const value = array[index]; 10 | if (callbackFn(value, index, array)) { 11 | return value; 12 | } 13 | } 14 | return undefined; 15 | } 16 | 17 | const found = find(array1, (element) => element > 10); 18 | console.log(found); 19 | -------------------------------------------------------------------------------- /polyfills/array/findIndex.js: -------------------------------------------------------------------------------- 1 | const array1 = [5, 12, 8, 130, 44]; 2 | 3 | // const found = array1.findIndex((element) => element > 10); 4 | // console.log(found); 5 | 6 | function findIndex(array, callbackFn) { 7 | for (let index = 0; index < array.length; index++) { 8 | const value = array[index]; 9 | if (callbackFn(value, index, array)) { 10 | return index; 11 | } 12 | } 13 | return -1; 14 | } 15 | 16 | const found = findIndex(array1, (element) => element > 10); 17 | console.log(found); 18 | -------------------------------------------------------------------------------- /polyfills/array/findLast.js: -------------------------------------------------------------------------------- 1 | const array1 = [5, 12, 8, 130, 44]; 2 | 3 | // const found = array1.findLast((element) => element > 50); 4 | // console.log(found); 5 | 6 | function findLast(array, callbackFn) { 7 | for (let index = array.length - 1; index >= 0; index--) { 8 | const value = array[index]; 9 | if (callbackFn(value, index, array)) { 10 | return value; 11 | } 12 | } 13 | return undefined; 14 | } 15 | 16 | const found = findLast(array1, (element) => element > 50); 17 | console.log(found); 18 | -------------------------------------------------------------------------------- /polyfills/array/findLastIndex.js: -------------------------------------------------------------------------------- 1 | const array1 = [5, 12, 8, 130, 44]; 2 | 3 | // const found = array1.findLastIndex((element) => element > 10); 4 | // console.log(found); 5 | 6 | function findLastIndex(array, callbackFn) { 7 | for (let index = array.length - 1; index >= 0; index--) { 8 | const value = array[index]; 9 | if (callbackFn(value, index, array)) { 10 | return index; 11 | } 12 | } 13 | return -1; 14 | } 15 | 16 | const found = findLastIndex(array1, (element) => element > 10); 17 | console.log(found); 18 | -------------------------------------------------------------------------------- /polyfills/array/flat.js: -------------------------------------------------------------------------------- 1 | const arr1 = [0, 1, [2, [3, [4, 5]]]]; 2 | 3 | // console.log(arr1.flat()); 4 | // console.log(arr1.flat(2)); 5 | 6 | function flat(array, depth = 1) { 7 | let result = []; 8 | array.forEach((item) => { 9 | if (Array.isArray(item) && depth > 0) { 10 | result.push(...flat(item, depth - 1)); 11 | } else { 12 | result.push(item); 13 | } 14 | }); 15 | 16 | return result; 17 | } 18 | 19 | console.log(flat(arr1)); 20 | console.log(flat(arr1, 2)); 21 | -------------------------------------------------------------------------------- /polyfills/array/forEach.js: -------------------------------------------------------------------------------- 1 | const array1 = ["a", "b", "c"]; 2 | // array1.forEach((element) => console.log(element)); 3 | 4 | function forEach(array, callbackFn) { 5 | for (let index = 0; index < array.length; index++) { 6 | const value = array[index]; 7 | callbackFn(value, index, array); 8 | } 9 | } 10 | forEach(array1, (element) => console.log(element)); 11 | -------------------------------------------------------------------------------- /polyfills/array/includes.js: -------------------------------------------------------------------------------- 1 | const array1 = [1, 2, 3]; 2 | 3 | // console.log(array1.includes(2)); 4 | 5 | function includes(array, searchElement, fromIndex = 0) { 6 | for (let index = fromIndex; index < array.length; index++) { 7 | const value = array[index]; 8 | if (value === searchElement) { 9 | return true; 10 | } 11 | } 12 | 13 | return false; 14 | } 15 | 16 | console.log(includes(array1, 2)); 17 | -------------------------------------------------------------------------------- /polyfills/array/indexOf.js: -------------------------------------------------------------------------------- 1 | const beasts = ["ant", "bison", "camel", "duck", "bison"]; 2 | 3 | // console.log(beasts.indexOf("bison")); 4 | 5 | function indexOf(array, searchElement, fromIndex = 0) { 6 | for (let index = fromIndex; index < array.length; index++) { 7 | const value = array[index]; 8 | if (value === searchElement) { 9 | return index; 10 | } 11 | } 12 | 13 | return -1; 14 | } 15 | 16 | console.log(indexOf(beasts, "bison")); 17 | -------------------------------------------------------------------------------- /polyfills/array/join.js: -------------------------------------------------------------------------------- 1 | const elements = ["Fire", "Air", "Water"]; 2 | 3 | // console.log(elements.join()); 4 | // console.log(elements.join("-")); 5 | 6 | function join(array, separator = ",") { 7 | if (array.length === 0) { 8 | return ""; 9 | } 10 | 11 | let result = ""; 12 | for (let index = 0; index < array.length; index++) { 13 | const value = array[index]; 14 | if (index > 0) { 15 | result = result + separator; 16 | } 17 | result = result + value; 18 | } 19 | 20 | return result; 21 | } 22 | 23 | console.log(join(elements)); 24 | console.log(join(elements, "-")); 25 | -------------------------------------------------------------------------------- /polyfills/array/keys.js: -------------------------------------------------------------------------------- 1 | const array1 = ["a", "b", "c"]; 2 | 3 | // const iterator = array1.keys(); 4 | // for (const key of iterator) { 5 | // console.log(key); 6 | // } 7 | 8 | function keys(array) { 9 | function* generator() { 10 | for (let index = 0; index < array.length; index++) { 11 | yield index; 12 | } 13 | } 14 | return generator(); 15 | } 16 | 17 | const iterator = array1.keys(); 18 | for (const key of iterator) { 19 | console.log(key); 20 | } 21 | -------------------------------------------------------------------------------- /polyfills/array/lastIndexOf.js: -------------------------------------------------------------------------------- 1 | const beasts = ["ant", "bison", "camel", "duck", "bison"]; 2 | 3 | // console.log(beasts.lastIndexOf("bison")); 4 | 5 | function lastIndexOf(array, searchElement) { 6 | for (let index = array.length - 1; index >= 0; index--) { 7 | const value = array[index]; 8 | if (value === searchElement) { 9 | return index; 10 | } 11 | } 12 | 13 | return -1; 14 | } 15 | 16 | console.log(lastIndexOf(beasts, "bison")); 17 | -------------------------------------------------------------------------------- /polyfills/array/map.js: -------------------------------------------------------------------------------- 1 | const array1 = [1, 4, 9, 16]; 2 | 3 | // const map1 = array1.map((x) => x * 2); 4 | // console.log(map1); 5 | 6 | function map(array, callbackFn) { 7 | let result = []; 8 | for (let index = 0; index < array.length; index++) { 9 | const value = array[index]; 10 | result.push(callbackFn(value, index, array)); 11 | } 12 | return result; 13 | } 14 | 15 | const map1 = map(array1, (x) => x * 2); 16 | console.log(map1); 17 | -------------------------------------------------------------------------------- /polyfills/array/pop.js: -------------------------------------------------------------------------------- 1 | const plants = ["broccoli", "cauliflower", "cabbage", "kale", "tomato"]; 2 | 3 | // console.log(plants.pop()); 4 | 5 | function pop(array) { 6 | if (array.length === 0) { 7 | return undefined; 8 | } 9 | 10 | const value = array[array.length - 1]; 11 | array.length = array.length - 1; 12 | return value; 13 | } 14 | 15 | console.log(pop(plants)); 16 | console.log(plants); 17 | -------------------------------------------------------------------------------- /polyfills/array/push.js: -------------------------------------------------------------------------------- 1 | const animals = ["pigs", "goats", "sheep"]; 2 | 3 | // const count = animals.push("cows"); 4 | // console.log(count); 5 | // console.log(animals); 6 | 7 | function push(array, ...values) { 8 | let arrayLength = array.length; 9 | for (let index = 0; index < values.length; index++) { 10 | array[arrayLength + index] = values[index]; 11 | } 12 | return array.length; 13 | } 14 | 15 | const count = push(animals, "cows"); 16 | console.log(count); 17 | console.log(animals); 18 | -------------------------------------------------------------------------------- /polyfills/array/reduce.js: -------------------------------------------------------------------------------- 1 | const array1 = [1, 2, 3, 4]; 2 | 3 | // 0 + 1 + 2 + 3 + 4 4 | // const initialValue = 0; 5 | // const sumWithInitial = array1.reduce( 6 | // (accumulator, currentValue) => accumulator + currentValue, 7 | // initialValue 8 | // ); 9 | 10 | function reduce(array, callbackFn, initialValue) { 11 | let accumulator = initialValue; 12 | for (let index = 0; index < array.length; index++) { 13 | const value = array[index]; 14 | if (accumulator) { 15 | accumulator = callbackFn(accumulator, value, index, array); 16 | } else { 17 | accumulator = value; 18 | } 19 | } 20 | 21 | return accumulator; 22 | } 23 | 24 | const initialValue = 0; 25 | const sumWithInitial = reduce( 26 | array1, 27 | (accumulator, currentValue) => accumulator + currentValue, 28 | initialValue 29 | ); 30 | 31 | console.log(sumWithInitial); 32 | -------------------------------------------------------------------------------- /polyfills/array/reverse.js: -------------------------------------------------------------------------------- 1 | const array1 = ["one", "two", "three"]; 2 | // const reversed = array1.reverse(); 3 | // console.log(reversed); 4 | // console.log(array1); 5 | 6 | function reverse(array) { 7 | for (let index = 0; index < array.length / 2; index++) { 8 | // [array[array.length - 1 - index], array[index]] = [ 9 | // array[index], 10 | // array[array.length - 1 - index], 11 | // ]; 12 | let temp = array[index]; 13 | array[index] = array[array.length - 1 - index]; 14 | array[array.length - 1 - index] = temp; 15 | } 16 | return array; 17 | } 18 | 19 | const reversed = reverse(array1); 20 | console.log(reversed); 21 | console.log(array1); 22 | -------------------------------------------------------------------------------- /polyfills/array/shift.js: -------------------------------------------------------------------------------- 1 | const array1 = [1, 2, 3]; 2 | 3 | // const firstElement = array1.shift(); 4 | // console.log(array1); 5 | 6 | function shift(array) { 7 | if (array.length === 0) { 8 | return undefined; 9 | } 10 | 11 | let firstValue = array[0]; 12 | for (let index = 1; index < array.length; index++) { 13 | const value = array[index]; 14 | array[index - 1] = value; 15 | } 16 | 17 | array.length = array.length - 1; 18 | return firstValue; 19 | } 20 | 21 | const firstElement = shift(array1); 22 | console.log({ firstElement, array1 }); 23 | -------------------------------------------------------------------------------- /polyfills/array/slice.js: -------------------------------------------------------------------------------- 1 | const animals = ["ant", "bison", "camel", "duck", "elephant"]; 2 | 3 | // console.log(animals.slice(2)); 4 | 5 | function slice(array, startIndex = 0, endIndex = array.length) { 6 | const result = []; 7 | for (let index = startIndex; index < endIndex; index++) { 8 | const value = array[index]; 9 | if (index < array.length) { 10 | result.push(value); 11 | } 12 | } 13 | return result; 14 | } 15 | 16 | console.log(slice(animals, 2)); 17 | console.log(slice(animals, 2, 4)); 18 | -------------------------------------------------------------------------------- /polyfills/array/some.js: -------------------------------------------------------------------------------- 1 | const array = [1, 2, 3, 4, 5]; 2 | 3 | const even = (element) => element % 2 === 0; 4 | // console.log(array.some(even)); 5 | 6 | function some(array, callbackFn) { 7 | for (let index = 0; index < array.length; index++) { 8 | const value = array[index]; 9 | if (callbackFn(value, index, array)) { 10 | return true; 11 | } 12 | } 13 | 14 | return false; 15 | } 16 | 17 | console.log(some(array, even)); 18 | -------------------------------------------------------------------------------- /polyfills/array/unshift.js: -------------------------------------------------------------------------------- 1 | const array1 = [1, 2, 3]; 2 | 3 | // console.log(array1.unshift(4, 5)); 4 | // console.log(array1); 5 | 6 | function unshift(array, ...values) { 7 | let mergedArrays = [...values, ...array]; 8 | for (let index = 0; index < mergedArrays.length; index++) { 9 | const value = mergedArrays[index]; 10 | array[index] = value; 11 | } 12 | 13 | return array.length; 14 | } 15 | 16 | console.log(unshift(array1, 4, 5)); 17 | console.log(array1); 18 | -------------------------------------------------------------------------------- /polyfills/array/values.js: -------------------------------------------------------------------------------- 1 | const array1 = ["a", "b", "c"]; 2 | 3 | // const iterator = array1.values(); 4 | // for (const key of iterator) { 5 | // console.log(key); 6 | // } 7 | 8 | function values(array) { 9 | function* generator() { 10 | for (let index = 0; index < array.length; index++) { 11 | yield array[index]; 12 | } 13 | } 14 | return generator(); 15 | } 16 | 17 | const iterator = values(array1); 18 | for (const key of iterator) { 19 | console.log(key); 20 | } 21 | -------------------------------------------------------------------------------- /polyfills/functions/apply.js: -------------------------------------------------------------------------------- 1 | Function.prototype.myApply = function (context = {}, args) { 2 | // `fn` is the function that `myApply` is being invoked on 3 | const fn = this; 4 | 5 | // Check if `fn` is actually a function 6 | if (typeof fn !== "function") { 7 | throw new Error("It's not callable"); 8 | } 9 | 10 | // Ensure `args` is an array 11 | if (!Array.isArray(args)) { 12 | throw new TypeError("Second argument must be an array"); 13 | } 14 | 15 | // Create a unique symbol to avoid property name collisions 16 | const key = Symbol(); 17 | 18 | // Temporarily assign `fn` as a property of `context` 19 | context[key] = fn; 20 | 21 | // Call the function with the provided `context` and arguments 22 | const result = context[key](...args); 23 | 24 | // Remove the temporary property from `context` 25 | delete context[key]; 26 | 27 | // Return the result of the function call 28 | return result; 29 | }; 30 | 31 | // Test Case 1: Basic Usage 32 | function greet(greeting, name) { 33 | return `${greeting}, ${name}!`; 34 | } 35 | const context1 = {}; 36 | console.log(greet.myApply(context1, ["Hello", "Alice"])); // Output: "Hello, Alice!" 37 | 38 | // Test Case 2: With a Custom Context Object 39 | const context2 = { name: "Bob" }; 40 | function introduce(role) { 41 | return `${this.name} is a ${role}.`; 42 | } 43 | console.log(introduce.myApply(context2, ["developer"])); // Output: "Bob is a developer." 44 | 45 | // Test Case 3: Handling Non-Function Context 46 | try { 47 | const nonFunction = {}; 48 | nonFunction.myApply = Function.prototype.myApply; 49 | nonFunction.myApply([], []); // This should throw an error 50 | } catch (error) { 51 | console.error(error.message); // Output: "It's not callable" 52 | } 53 | 54 | // Test Case 4: `args` Not an Array 55 | try { 56 | function sayGoodbye(name) { 57 | return `Goodbye, ${name}!`; 58 | } 59 | sayGoodbye.myApply(null, "Charlie"); // This should throw a TypeError 60 | } catch (error) { 61 | console.error(error.message); // Output: "Second argument must be an array" 62 | } 63 | -------------------------------------------------------------------------------- /polyfills/functions/bind.js: -------------------------------------------------------------------------------- 1 | Function.prototype.myBind = function (context = {}, ...args) { 2 | // `fn` is the function that `myBind` is being invoked on 3 | const fn = this; 4 | 5 | // Check if `fn` is actually a function 6 | if (typeof fn !== "function") { 7 | throw new Error("It's not callable"); 8 | } 9 | 10 | // Create a unique symbol to avoid property name collisions 11 | const key = Symbol(); 12 | 13 | // Temporarily assign `fn` as a property of `context` 14 | context[key] = fn; 15 | 16 | // Return a new function that, when called, will invoke `fn` with the given `context` and arguments 17 | return function (...newArgs) { 18 | return context[key](...args, ...newArgs); 19 | }; 20 | }; 21 | 22 | // Test Case 1: Basic Usage 23 | function greet(greeting, name) { 24 | return `${greeting}, ${name}!`; 25 | } 26 | const boundGreet = greet.myBind({}, "Hello"); 27 | console.log(boundGreet("Alice")); // Output: "Hello, Alice!" 28 | 29 | // Test Case 2: With a Custom Context Object 30 | const context = { name: "Bob" }; 31 | function introduce(role) { 32 | return `${this.name} is a ${role}.`; 33 | } 34 | const boundIntroduce = introduce.myBind(context); 35 | console.log(boundIntroduce("developer")); // Output: "Bob is a developer." 36 | 37 | // Test Case 3: Handling Non-Function Context 38 | try { 39 | const nonFunction = {}; 40 | nonFunction.myBind = Function.prototype.myBind; 41 | nonFunction.myBind(); // This should throw an error 42 | } catch (error) { 43 | console.error(error.message); // Output: "It's not callable" 44 | } 45 | 46 | // Test Case 4: Pre-set Arguments 47 | function multiply(a, b) { 48 | return a * b; 49 | } 50 | const boundMultiply = multiply.myBind({}, 2); 51 | console.log(boundMultiply(3)); // Output: 6 52 | 53 | // Test Case 5: Multiple Pre-set and New Arguments 54 | function add(a, b, c) { 55 | return a + b + c; 56 | } 57 | const boundAdd = add.myBind({}, 1, 2); 58 | console.log(boundAdd(3)); // Output: 6 59 | 60 | // Test Case 6: Empty `args` 61 | function greetEveryone() { 62 | return "Hello, everyone!"; 63 | } 64 | const boundGreetEveryone = greetEveryone.myBind({}); 65 | console.log(boundGreetEveryone()); // Output: "Hello, everyone!" 66 | -------------------------------------------------------------------------------- /polyfills/functions/call.js: -------------------------------------------------------------------------------- 1 | Function.prototype.myCall = function (context = {}, ...args) { 2 | // `fn` is the function that `myCall` is being invoked on 3 | const fn = this; 4 | 5 | // Check if `fn` is actually a function 6 | if (typeof fn !== "function") { 7 | throw new Error("It's not callable"); 8 | } 9 | 10 | // Create a unique symbol to avoid property name collisions 11 | const key = Symbol(); 12 | 13 | // Temporarily assign `fn` as a property of `context` 14 | context[key] = fn; 15 | 16 | // Call the function with the provided `context` and arguments 17 | const result = context[key](...args); 18 | 19 | // Remove the temporary property from `context` 20 | delete context[key]; 21 | 22 | // Return the result of the function call 23 | return result; 24 | }; 25 | 26 | // Test Case 1: Basic Usage 27 | function greet(greeting, name) { 28 | return `${greeting}, ${name}!`; 29 | } 30 | const context1 = {}; 31 | console.log(greet.myCall(context1, "Hello", "Alice")); // Output: "Hello, Alice!" 32 | 33 | // Test Case 2: With a Custom Context Object 34 | const context2 = { name: "Bob" }; 35 | function introduce(role) { 36 | return `${this.name} is a ${role}.`; 37 | } 38 | console.log(introduce.myCall(context2, "developer")); // Output: "Bob is a developer." 39 | 40 | // Test Case 3: Handling Non-Function Context 41 | try { 42 | const nonFunction = {}; 43 | nonFunction.myCall = Function.prototype.myCall; 44 | nonFunction.myCall(); // This should throw an error 45 | } catch (error) { 46 | console.error(error.message); // Output: "It's not callable" 47 | } 48 | -------------------------------------------------------------------------------- /polyfills/object/Object.assign.js: -------------------------------------------------------------------------------- 1 | function ObjectAssign(target, ...sources) { 2 | // Ensure the target is an object; throw an error if it's null or undefined 3 | if (target === null || target === undefined) { 4 | throw new TypeError("Target must be an object"); 5 | } 6 | 7 | // Convert target to an object if it's not already 8 | target = Object(target); 9 | 10 | // Iterate over each source object provided as arguments 11 | for (let source of sources) { 12 | // Skip null or undefined sources 13 | if (source === null || source === undefined) { 14 | continue; 15 | } 16 | 17 | // Convert source to an object if it's not already 18 | source = Object(source); 19 | // By ensuring that target and source are objects, one avoid issues related 20 | // to property access and assignment that would otherwise occur with primitives. 21 | // This conversion makes sure that one can safely use methods like 22 | // Object.getOwnPropertyDescriptor and Object.defineProperty. 23 | 24 | // Get all property keys (including symbols) from the source object 25 | const keys = Reflect.ownKeys(source); 26 | // Get property descriptors for all properties of the source object 27 | const descriptors = Object.getOwnPropertyDescriptors(source); 28 | 29 | // Iterate over each key in the source object 30 | keys.forEach((key) => { 31 | // Get the property descriptor for the target object for the current key 32 | const targetDescriptor = Object.getOwnPropertyDescriptor(target, key); 33 | 34 | // If the target property exists and is not writable, throw an error 35 | if (targetDescriptor && targetDescriptor.writable === false) { 36 | throw new Error(`Property ${key} is not writable to target`); 37 | } 38 | 39 | // Only copy properties if they are enumerable 40 | if (descriptors[key].enumerable) { 41 | target[key] = source[key]; 42 | } 43 | }); 44 | } 45 | 46 | // Return the updated target object 47 | return target; 48 | } 49 | 50 | // Test Case 1: Basic Usage 51 | const target1 = { a: 1 }; 52 | const source1 = { b: 2, c: 3 }; 53 | console.log(ObjectAssign(target1, source1)); // { a: 1, b: 2, c: 3 } 54 | 55 | // Test Case 2: Overwriting Existing Properties 56 | const target2 = { a: 1, b: 2 }; 57 | const source2 = { b: 3, c: 4 }; 58 | console.log(ObjectAssign(target2, source2)); // { a: 1, b: 3, c: 4 } 59 | 60 | // Test Case 3: Handling Non-enumerable Properties 61 | const target3 = {}; 62 | const source3 = Object.defineProperty({}, "a", { 63 | value: 1, 64 | writable: true, 65 | enumerable: false, 66 | configurable: true, 67 | }); 68 | console.log(ObjectAssign(target3, source3)); // {} (non-enumerable properties are not copied) 69 | 70 | // Test Case 4: Handling Symbol Properties 71 | const symbolKey = Symbol("symbolKey"); 72 | const target4 = {}; 73 | const source4 = { [symbolKey]: "symbolValue" }; 74 | console.log(ObjectAssign(target4, source4)); // { [Symbol('symbolKey')]: 'symbolValue' } 75 | 76 | // Test Case 5: Null and Undefined Sources 77 | const target6 = { a: 1 }; 78 | console.log(ObjectAssign(target6, null, undefined, { b: 2 })); // { a: 1, b: 2 } (null and undefined sources are ignored) 79 | 80 | // Test Case 6: Target as Primitive 81 | const target7 = 3; // primitive value 82 | const source7 = { a: 1 }; 83 | console.log(ObjectAssign(target7, source7)); // [Number: 3] { a: 1 } (target is converted to an object) 84 | 85 | // Test Case 7: Target as Object and Multiple Sources 86 | const target8 = { a: 1 }; 87 | const source8 = { b: 2 }; 88 | const source9 = { c: 3 }; 89 | console.log(ObjectAssign(target8, source8, source9)); // { a: 1, b: 2, c: 3 } 90 | 91 | // Test Case 8: Throw Error for Non-writable Target Property 92 | const target9 = Object.defineProperty({}, "a", { 93 | value: 1, 94 | writable: false, 95 | enumerable: true, 96 | }); 97 | const source10 = { a: 2 }; 98 | try { 99 | console.log(ObjectAssign(target9, source10)); 100 | } catch (e) { 101 | console.error(e.message); // Property a is not writable to target 102 | } 103 | -------------------------------------------------------------------------------- /polyfills/object/Object.is.js: -------------------------------------------------------------------------------- 1 | function ObjectIs(a, b) { 2 | // Special case: Both values are NaN 3 | if (Number.isNaN(a) && Number.isNaN(b)) { 4 | return true; 5 | } 6 | 7 | // Special case: Distinguish between +0 and -0 8 | if (a === 0 && b === 0) { 9 | // 1 / 0 results in Infinity 10 | // 1 / -0 results in -Infinity 11 | return 1 / a === 1 / b; 12 | } 13 | 14 | // Default comparison 15 | return a === b; 16 | } 17 | // Primitive values 18 | console.log(ObjectIs(0, -0) === Object.is(0, -0)); // true 19 | console.log(ObjectIs(-0, -0) === Object.is(-0, -0)); // true 20 | console.log(ObjectIs(NaN, NaN) === Object.is(NaN, NaN)); // true 21 | console.log(ObjectIs(5, 5) === Object.is(5, 5)); // true 22 | console.log(ObjectIs(5, "5") === Object.is(5, "5")); // true 23 | console.log(ObjectIs(null, null) === Object.is(null, null)); // true 24 | console.log(ObjectIs(undefined, null) === Object.is(undefined, null)); // true 25 | 26 | // Object references 27 | const obj1 = {}; 28 | const obj2 = {}; 29 | console.log(ObjectIs(obj1, obj2) === Object.is(obj1, obj2)); // true 30 | console.log(ObjectIs(obj1, obj1) === Object.is(obj1, obj1)); // true 31 | 32 | const arr1 = []; 33 | const arr2 = []; 34 | console.log(ObjectIs(arr1, arr2) === Object.is(arr1, arr2)); // true 35 | console.log(ObjectIs(arr1, arr1) === Object.is(arr1, arr1)); // true 36 | 37 | // Mixed object/array comparisons 38 | console.log(ObjectIs({}, []) === Object.is({}, [])); // true 39 | console.log(ObjectIs([], []) === Object.is([], [])); // true 40 | 41 | // Complex nested comparisons 42 | const nestedObj1 = { key: { nested: 1 } }; 43 | const nestedObj2 = { key: { nested: 1 } }; 44 | console.log( 45 | ObjectIs(nestedObj1, nestedObj2) === Object.is(nestedObj1, nestedObj2) 46 | ); // true 47 | 48 | // Null and undefined comparisons 49 | console.log(ObjectIs(null, undefined) === Object.is(null, undefined)); // true 50 | console.log(ObjectIs(undefined, undefined) === Object.is(undefined, undefined)); // true 51 | console.log(ObjectIs(null, null) === Object.is(null, null)); // true 52 | 53 | // NaN comparisons 54 | console.log(ObjectIs(NaN, NaN) === Object.is(NaN, NaN)); // true 55 | -------------------------------------------------------------------------------- /polyfills/promises/promise.all.js: -------------------------------------------------------------------------------- 1 | Promise.customAll = function (promises) { 2 | let result = []; 3 | let resolvedPromises = 0; 4 | return new Promise((resolve, reject) => { 5 | if (promises.length === 0) { 6 | resolve(result); 7 | } 8 | 9 | promises.forEach((promise, index) => { 10 | Promise.resolve(promise) // to handle primitive values 11 | .then((value) => { 12 | result[index] = value; 13 | resolvedPromises++; 14 | 15 | if (resolvedPromises === promises.length) { 16 | resolve(result); 17 | } 18 | }) 19 | .catch((err) => reject(err)); 20 | }); 21 | }); 22 | }; 23 | 24 | const promise1 = Promise.customAll([]); 25 | 26 | promise1 27 | .then((value) => console.log("Resolved with 1 - ", value)) 28 | .catch((value) => console.error("Rejected with 1 - ", value)); 29 | // Resolved with 1 - [] 30 | 31 | const promise2 = Promise.customAll([ 32 | Promise.resolve(1), 33 | new Promise((resolve) => setTimeout(() => resolve(2), 2000)), 34 | Promise.resolve(3), 35 | 4, 36 | ]); 37 | 38 | promise2 39 | .then((value) => console.log("Resolved with 2 - ", value)) 40 | .catch((value) => console.error("Rejected with 2 - ", value)); 41 | // Resolved with 2 - [ 1, 2, 3, 4 ] 42 | 43 | const promise3 = Promise.customAll([ 44 | Promise.resolve(1), 45 | new Promise((resolve) => setTimeout(() => resolve(2), 2000)), 46 | Promise.resolve(3), 47 | Promise.reject(4), 48 | Promise.reject(5), 49 | ]); 50 | 51 | promise3 52 | .then((value) => console.log("Resolved with 3 - ", value)) 53 | .catch((value) => console.error("Rejected with 3 - ", value)); 54 | // Rejected with 3 - 4 55 | 56 | const promise4 = Promise.customAll([ 57 | null, 58 | undefined, 59 | new Promise((resolve) => setTimeout(() => resolve(2), 2000)), 60 | {}, 61 | 3, 62 | "hello", 63 | true, 64 | ]); 65 | 66 | promise4 67 | .then((value) => console.log("Resolved with 4 - ", value)) 68 | .catch((value) => console.error("Rejected with 4 - ", value)); 69 | // Resolved with 4 - [ null, undefined, 2, {}, 3, 'hello', true ] 70 | -------------------------------------------------------------------------------- /polyfills/promises/promise.allSettled.js: -------------------------------------------------------------------------------- 1 | Promise.customAllSettled = function (promises) { 2 | let result = []; 3 | let settledPromises = 0; 4 | return new Promise((resolve, reject) => { 5 | if (promises.length === 0) { 6 | resolve(result); 7 | } 8 | 9 | promises.forEach((promise, index) => { 10 | Promise.resolve(promise) // to handle primitive values 11 | .then((value) => { 12 | result[index] = { status: "Fulfilled", value }; 13 | settledPromises++; 14 | 15 | if (settledPromises === promises.length) { 16 | resolve(result); 17 | } 18 | }) 19 | .catch((reason) => { 20 | result[index] = { status: "Rejected", reason }; 21 | settledPromises++; 22 | 23 | if (settledPromises === promises.length) { 24 | resolve(result); 25 | } 26 | }); 27 | }); 28 | }); 29 | }; 30 | 31 | const promise1 = Promise.customAllSettled([]); 32 | 33 | promise1 34 | .then((value) => console.log("Resolved with 1 - ", value)) 35 | .catch((value) => console.error("Rejected with 1 - ", value)); 36 | // Resolved with 1 - [] 37 | 38 | const promise2 = Promise.customAllSettled([ 39 | Promise.resolve(1), 40 | new Promise((resolve) => setTimeout(() => resolve(2), 2000)), 41 | Promise.resolve(3), 42 | 4, 43 | ]); 44 | 45 | promise2 46 | .then((value) => console.log("Resolved with 2 - ", value)) 47 | .catch((value) => console.error("Rejected with 2 - ", value)); 48 | /* 49 | Resolved with 2 - [ 50 | { status: 'Fulfilled', value: 1 }, 51 | { status: 'Fulfilled', value: 2 }, 52 | { status: 'Fulfilled', value: 3 }, 53 | { status: 'Fulfilled', value: 4 } 54 | ] 55 | */ 56 | 57 | const promise3 = Promise.customAllSettled([ 58 | Promise.resolve(1), 59 | new Promise((resolve) => setTimeout(() => resolve(2), 2000)), 60 | Promise.resolve(3), 61 | Promise.reject(4), 62 | Promise.reject(5), 63 | ]); 64 | 65 | promise3 66 | .then((value) => console.log("Resolved with 3 - ", value)) 67 | .catch((value) => console.error("Rejected with 3 - ", value)); 68 | /* 69 | Resolved with 3 - [ 70 | { status: 'Fulfilled', value: 1 }, 71 | { status: 'Fulfilled', value: 2 }, 72 | { status: 'Fulfilled', value: 3 }, 73 | { status: 'Rejected', reason: 4 }, 74 | { status: 'Rejected', reason: 5 } 75 | ] 76 | */ 77 | 78 | const promise4 = Promise.customAllSettled([ 79 | null, 80 | undefined, 81 | new Promise((resolve) => setTimeout(() => resolve(2), 2000)), 82 | {}, 83 | 3, 84 | "hello", 85 | true, 86 | ]); 87 | 88 | promise4 89 | .then((value) => console.log("Resolved with 4 - ", value)) 90 | .catch((value) => console.error("Rejected with 4 - ", value)); 91 | /* 92 | Resolved with 4 - [ 93 | { status: 'Fulfilled', value: null }, 94 | { status: 'Fulfilled', value: undefined }, 95 | { status: 'Fulfilled', value: 2 }, 96 | { status: 'Fulfilled', value: {} }, 97 | { status: 'Fulfilled', value: 3 }, 98 | { status: 'Fulfilled', value: 'hello' }, 99 | { status: 'Fulfilled', value: true } 100 | ] 101 | */ 102 | -------------------------------------------------------------------------------- /polyfills/promises/promise.any.js: -------------------------------------------------------------------------------- 1 | Promise.customAny = function (promises) { 2 | let errors = []; 3 | let rejectedPromises = 0; 4 | 5 | return new Promise((resolve, reject) => { 6 | if (promises.length === 0) { 7 | reject(new AggregateError(errors, "Empty Array")); 8 | } 9 | 10 | promises.forEach((promise, index) => { 11 | Promise.resolve(promise) // to handle primitive values 12 | .then((value) => { 13 | resolve(value); 14 | }) 15 | .catch((reason) => { 16 | errors[index] = reason; 17 | rejectedPromises++; 18 | 19 | if (rejectedPromises === promises.length) { 20 | reject(new AggregateError(errors, "All promises rejected")); 21 | } 22 | }); 23 | }); 24 | }); 25 | }; 26 | 27 | const promise1 = Promise.customAny([]); 28 | 29 | promise1 30 | .then((value) => console.log("Resolved with 1 - ", value)) 31 | .catch((value) => console.error("Rejected with 1 - ", value)); 32 | /* 33 | Rejected with 1 - AggregateError: Empty Array 34 | at E:\interview-preparation\javascript-interview-preparation\polyfills\promises\promise.any.js:7:14 35 | at new Promise () 36 | at Promise.customAny (E:\interview-preparation\javascript-interview-preparation\polyfills\promises\promise.any.js:5:10) 37 | at Object. (E:\interview-preparation\javascript-interview-preparation\polyfills\promises\promise.any.js:27:26) 38 | at Module._compile (node:internal/modules/cjs/loader:1256:14) 39 | at Module._extensions..js (node:internal/modules/cjs/loader:1310:10) 40 | at Module.load (node:internal/modules/cjs/loader:1119:32) 41 | at Module._load (node:internal/modules/cjs/loader:960:12) 42 | at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:86:12) 43 | at node:internal/main/run_main_module:23:47 { 44 | [errors]: [] 45 | } 46 | */ 47 | 48 | const promise2 = Promise.customAny([ 49 | Promise.resolve(1), 50 | new Promise((resolve) => setTimeout(() => resolve(2), 2000)), 51 | Promise.resolve(3), 52 | 4, 53 | ]); 54 | 55 | promise2 56 | .then((value) => console.log("Resolved with 2 - ", value)) 57 | .catch((value) => console.error("Rejected with 2 - ", value)); 58 | // Resolved with 2 - 1 59 | 60 | const promise3 = Promise.customAny([ 61 | Promise.reject(1), 62 | new Promise((resolve, reject) => setTimeout(() => reject(2), 2000)), 63 | Promise.reject(3), 64 | Promise.reject(4), 65 | Promise.reject(5), 66 | ]); 67 | 68 | promise3 69 | .then((value) => console.log("Resolved with 3 - ", value)) 70 | .catch((value) => console.error("Rejected with 3 - ", value)); 71 | /* 72 | Rejected with 3 - AggregateError: All promises rejected 73 | at E:\interview-preparation\javascript-interview-preparation\polyfills\promises\promise.any.js:20:20 74 | at runNextTicks (node:internal/process/task_queues:60:5) 75 | at listOnTimeout (node:internal/timers:538:9) 76 | at process.processTimers (node:internal/timers:512:7) { 77 | [errors]: [ 1, 2, 3, 4, 5 ] 78 | */ 79 | 80 | const promise4 = Promise.customAny([ 81 | null, 82 | undefined, 83 | new Promise((resolve) => setTimeout(() => resolve(2), 2000)), 84 | {}, 85 | 3, 86 | "hello", 87 | true, 88 | ]); 89 | 90 | promise4 91 | .then((value) => console.log("Resolved with 4 - ", value)) 92 | .catch((value) => console.error("Rejected with 4 - ", value)); 93 | // Resolved with 4 - null 94 | -------------------------------------------------------------------------------- /polyfills/string/trim.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Custom trim function to remove leading and trailing whitespace characters. 3 | * @param {string} str - The string to be trimmed. 4 | * @returns {string} - The trimmed string. 5 | */ 6 | function trim(str) { 7 | /** 8 | * Determines if a character is a whitespace character. 9 | * @param {string} ch - The character to be checked. 10 | * @returns {boolean} - True if the character is a whitespace character. 11 | */ 12 | function isWhiteSpaceCharacter(ch) { 13 | // Check if the character is a space, tab, or newline. 14 | return [" ", "\t", "\n"].includes(ch); 15 | } 16 | 17 | // Initialize pointers for the start and end of the string. 18 | let start = 0; 19 | let end = str.length - 1; 20 | 21 | // Move the start pointer forward until a non-whitespace character is found. 22 | while (start < str.length && isWhiteSpaceCharacter(str[start])) { 23 | start++; 24 | } 25 | 26 | // Move the end pointer backward until a non-whitespace character is found. 27 | while (end >= 0 && isWhiteSpaceCharacter(str[end])) { 28 | end--; 29 | } 30 | 31 | // Slice the string from start to end + 1 (inclusive) and return. 32 | return str.slice(start, end + 1); 33 | } 34 | 35 | // Test cases 36 | 37 | // Test with a string that has both leading and trailing whitespace. 38 | const s = " Hello World "; 39 | console.log(trim(s)); // "Hello World" 40 | console.log(s.trim() === trim(s)); // true (comparison with native trim) 41 | 42 | // Test with only leading whitespace. 43 | console.log(trim(" Leading whitespace")); // "Leading whitespace" 44 | 45 | // Test with only trailing whitespace. 46 | console.log(trim("Trailing whitespace ")); // "Trailing whitespace" 47 | 48 | // Test with both leading and trailing whitespace. 49 | console.log(trim(" Both sides ")); // "Both sides" 50 | 51 | // Test with a string that has no whitespace. 52 | console.log(trim("NoWhitespace")); // "NoWhitespace" 53 | 54 | // Test with a string that has only whitespace characters. 55 | console.log(trim(" ").length === 0); // "" 56 | 57 | // Test with an empty string. 58 | console.log(trim("").length === 0); // "" 59 | 60 | // Test with tab and newline characters. 61 | console.log(trim("\t\tTabs and newlines\n\n")); // "Tabs and newlines" 62 | 63 | // Test with a mix of spaces, tabs, and newlines. 64 | console.log(trim(" \t\n Mixed whitespace \n\t ")); // "Mixed whitespace" 65 | 66 | // Edge case: single character string with no whitespace. 67 | console.log(trim("A")); // "A" 68 | 69 | // Edge case: single character string with whitespace. 70 | console.log(trim(" ").length === 0); // "" 71 | -------------------------------------------------------------------------------- /polyfills/window/setInterval.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Creates a custom setInterval function with encapsulated state. 3 | * 4 | * @returns {Object} - An object with customSetInterval and customClearInterval methods. 5 | */ 6 | function createCustomSetInterval() { 7 | const timeoutMap = new Map(); // Stores the timer IDs for active intervals 8 | let nextId = 1; // Tracks the next unique interval ID 9 | 10 | /** 11 | * Custom implementation of setInterval using Node.js setTimeout. 12 | * 13 | * @param {Function} callback - The function to execute repeatedly. 14 | * @param {number} interval - The number of milliseconds to wait between executions. 15 | * @param {...*} args - The arguments to pass to the callback when it is executed. 16 | * @returns {number} - The interval ID which can be used to cancel the interval with customClearInterval. 17 | */ 18 | function customSetInterval(callback, interval, ...args) { 19 | let id = nextId++; // Generate a new unique interval ID 20 | 21 | // Function to schedule the next execution of the callback 22 | function scheduleNext() { 23 | const timerId = setTimeout(() => { 24 | callback(...args); // Execute the callback with provided arguments 25 | 26 | // Schedule the next execution if the interval hasn't been cleared 27 | if (timeoutMap.has(id)) { 28 | scheduleNext(); 29 | } 30 | }, interval); 31 | timeoutMap.set(id, timerId); // Store the timer ID in the map 32 | } 33 | 34 | scheduleNext(); // Start the first execution 35 | 36 | return id; // Return the interval ID for later clearing 37 | } 38 | 39 | /** 40 | * Custom implementation of clearInterval using Node.js clearTimeout. 41 | * 42 | * @param {number} id - The interval ID returned by customSetInterval. 43 | */ 44 | function customClearInterval(id) { 45 | const timerId = timeoutMap.get(id); // Retrieve the timer ID associated with the interval ID 46 | 47 | if (timerId) { 48 | clearTimeout(timerId); // Cancel the timeout 49 | timeoutMap.delete(id); // Remove the interval ID from the map 50 | } 51 | } 52 | 53 | return { 54 | customSetInterval, 55 | customClearInterval, 56 | }; 57 | } 58 | 59 | // Usage example: 60 | const { customSetInterval, customClearInterval } = createCustomSetInterval(); 61 | 62 | // Set up an interval that logs a message every 1000 milliseconds 63 | const intervalId = customSetInterval( 64 | (message) => { 65 | console.log(message); // Log the message 66 | }, 67 | 1000, // Interval of 1000 milliseconds (1 second) 68 | "Custom interval callback executed!" // Message to log 69 | ); 70 | 71 | // Optionally clear the interval after 10000 milliseconds (10 seconds) 72 | setTimeout(() => { 73 | console.log("Cleared Interval"); 74 | customClearInterval(intervalId); // Clear the interval 75 | }, 10000); 76 | 77 | const intervalId2 = customSetInterval( 78 | (message) => { 79 | console.log(message); // Log the message 80 | }, 81 | 2000, 82 | "Custom interval callback executed! 2" // Message to log 83 | ); 84 | 85 | setTimeout(() => { 86 | console.log("Cleared Interval 2"); 87 | customClearInterval(intervalId2); // Clear the interval 88 | }, 5000); 89 | 90 | // Output: 91 | 92 | // Custom interval callback executed! 93 | // Custom interval callback executed! 2 94 | // Custom interval callback executed! 95 | // Custom interval callback executed! 96 | // Custom interval callback executed! 2 97 | // Custom interval callback executed! 98 | // Cleared Interval 2 99 | // Custom interval callback executed! 100 | // Custom interval callback executed! 101 | // Custom interval callback executed! 102 | // Custom interval callback executed! 103 | // Custom interval callback executed! 104 | // Cleared Interval 105 | -------------------------------------------------------------------------------- /polyfills/window/setTimeout.js: -------------------------------------------------------------------------------- 1 | // IMPORTANT requestIdleCallback is browser web api 2 | 3 | /** 4 | * Creates a custom setTimeout function with encapsulated state. 5 | * 6 | * @returns {Object} - An object with customSetTimeout and customClearTimeout methods. 7 | */ 8 | function createCustomSetTimeout() { 9 | // Encapsulated state 10 | const timeoutMap = new Map(); 11 | let nextId = 1; 12 | 13 | /** 14 | * Custom implementation of setTimeout using requestIdleCallback. 15 | * 16 | * @param {Function} callback - The function to execute after the delay. 17 | * @param {number} delay - The number of milliseconds to wait before executing the callback. 18 | * @param {...*} args - The arguments to pass to the callback when it is executed. 19 | * @returns {number} - The timeout ID which can be used to cancel the timeout with customClearTimeout. 20 | */ 21 | function customSetTimeout(callback, delay, ...args) { 22 | const id = nextId++; 23 | let startTime = Date.now(); 24 | 25 | // Create an idle callback 26 | const idleCallbackId = requestIdleCallback( 27 | () => { 28 | if (Date.now() - startTime >= delay) { 29 | callback(...args); // Pass arguments to the callback 30 | timeoutMap.delete(id); // Remove the timeout entry after execution 31 | } else { 32 | // Schedule the callback again if not enough time has passed 33 | customSetTimeout(callback, delay - (Date.now() - startTime), ...args); 34 | } 35 | }, 36 | { timeout: delay } 37 | ); 38 | 39 | // Store the timeout ID and corresponding callback ID 40 | timeoutMap.set(id, idleCallbackId); 41 | 42 | // Return the timeout ID to allow cancellation 43 | return id; 44 | } 45 | 46 | /** 47 | * Custom implementation of clearTimeout using cancelIdleCallback. 48 | * 49 | * @param {number} id - The timeout ID returned by customSetTimeout. 50 | */ 51 | function customClearTimeout(id) { 52 | const idleCallbackId = timeoutMap.get(id); 53 | if (idleCallbackId) { 54 | cancelIdleCallback(idleCallbackId); 55 | timeoutMap.delete(id); // Remove the timeout entry after cancellation 56 | } 57 | } 58 | 59 | // Return an object with the custom functions 60 | return { 61 | customSetTimeout, 62 | customClearTimeout, 63 | }; 64 | } 65 | 66 | // Usage: 67 | const { customSetTimeout, customClearTimeout } = createCustomSetTimeout(); 68 | 69 | const timeoutId = customSetTimeout( 70 | (message) => { 71 | console.log(message); 72 | }, 73 | 2000, 74 | "Custom timeout callback executed!" 75 | ); // Executes the callback after approximately 2000 milliseconds with arguments 76 | 77 | const timeoutId2 = customSetTimeout( 78 | (message) => { 79 | console.log(message); 80 | }, 81 | 2500, 82 | "Custom timeout callback executed 2!" 83 | ); // Executes the callback after approximately 2000 milliseconds with arguments 84 | 85 | // Optionally clear the timeout if needed 86 | customClearTimeout(timeoutId2); 87 | --------------------------------------------------------------------------------