Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 67 additions & 1 deletion 01-deep-clone/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,29 +13,95 @@ function deepClone(value, visited = new WeakMap()) {

// Step 1: Handle primitives (return as-is)
// Primitives: null, undefined, number, string, boolean, symbol, bigint
switch(typeof value){
case 'null': return null;
case 'boolean':
case 'number':
case 'bigint':
case 'string':
case 'symbol':
case 'function':
case 'undefined':
return value;
}

// Step 2: Check for circular references using the visited WeakMap
// If we've seen this object before, return the cached clone
if(visited.has(value)){
return visited.get(value);
}

// Step 3: Handle Date objects
// Create a new Date with the same time value
if(value instanceof Date){
const clone = new Date(value.getTime());
visited.set(value, clone);
return clone;
}

// Step 4: Handle RegExp objects
// Create a new RegExp with the same source and flags
if(value instanceof RegExp){
const clone = new RegExp(value.source, value.flags);
visited.set(value, clone);
return clone;
}

// Step 5: Handle Map objects
// Create a new Map and deep clone each key-value pair
if(value instanceof Map){
const clone = new Map();
visited.set(value, clone);

for(let [key, v] of value.entries()){
clone.set(deepClone(key, visited), deepClone(v, visited));
}

return clone;
}

// Step 6: Handle Set objects
// Create a new Set and deep clone each value
if(value instanceof Set){
const clone = new Set();
visited.set(value, clone);

for(let v of value.values()){
clone.add(deepClone(v, visited));
}

return clone;
}

// Step 7: Handle Arrays
// Create a new array and deep clone each element
if(Array.isArray(value)){
const clone = [];
visited.set(value, clone);

for(let i = 0; i<value.length; i++){
clone[i] = deepClone(value[i], visited);
}

return clone;
}

// Step 8: Handle plain Objects
// Create a new object and deep clone each property
if(typeof value === 'object' && !Array.isArray(value) && value !== null){
const clone = {};
visited.set(value, clone);

const props = Object.getOwnPropertyNames(value);

for(const prop of props){
clone[prop] = deepClone(value[prop], visited);
}

return clone;
}

return undefined; // Broken: Replace with your implementation
return value; // Broken: Replace with your implementation
}

module.exports = { deepClone };
48 changes: 42 additions & 6 deletions 02-debounce-throttle/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,30 @@ function debounce(fn, delay) {
// TODO: Implement debounce

// Step 1: Create a variable to store the timeout ID
let timeoutId;

// Step 2: Create the debounced function that:
// - Clears any existing timeout
// - Sets a new timeout to call fn after delay
// - Preserves `this` context and arguments
const debounceFn = function(...args){
const context = this;

clearTimeout(timeoutId);

timeoutId = setTimeout(()=>{
fn.apply(context, args);
}, delay);
};

// Step 3: Add a cancel() method to clear pending timeout
debounceFn.cancel = function(){
clearTimeout(timeoutId);
timeoutId = null;
};

// Step 4: Return the debounced function

// Return a placeholder that doesn't work
throw new Error("Not implemented");
return debounceFn;
}

/**
Expand All @@ -43,17 +55,41 @@ function throttle(fn, limit) {
// - Whether we're currently in a throttle period
// - The timeout ID for cleanup

let isThrottle = false;
let timeoutId;
let lastArgs;
let lastContext;

// Step 2: Create the throttled function that:
// - If not throttling, execute fn immediately and start throttle period
// - If throttling, ignore the call
// - Preserves `this` context and arguments
const throttleFn = function(...args){
lastContext = this;
lastArgs = args;

if(!isThrottle){
fn.apply(this, args);

isThrottle = true;

timeoutId = setTimeout(()=>{
isThrottle = false;
}, limit);
}
};

// Step 3: Add a cancel() method to reset throttle state

// Step 4: Return the throttled function
throttleFn.cancel = function(){
clearTimeout(timeoutId);
isThrottle = false;
lastArgs = null;
lastContext = null;
}

// Return a placeholder that doesn't work
throw new Error("Not implemented");
// Step 4: Return the throttled function
return throttleFn;
}

module.exports = { debounce, throttle };
56 changes: 50 additions & 6 deletions 03-custom-bind/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ function customBind(fn, context, ...boundArgs) {

// Step 1: Validate that fn is a function
// Throw TypeError if not
if(typeof fn !== 'function'){
throw new TypeError('first arg must be function');
}

// Step 2: Create the bound function
// It should:
Expand All @@ -26,14 +29,31 @@ function customBind(fn, context, ...boundArgs) {
// When called as a constructor:
// - `this` should be a new instance, not the bound context
// - The prototype chain should be preserved
const bound = function(...args){
const isCalledAsConstructor = this instanceof bound;

const actualContext = isCalledAsConstructor ? this : context;

const argsFn = [...boundArgs, ...args];

return fn.apply(actualContext, argsFn);
}

// Step 4: Preserve the prototype for constructor usage
// boundFunction.prototype = Object.create(fn.prototype)
if(fn.prototype){
bound.prototype = Object.create(fn.prototype);

// Step 5: Return the bound function
Object.defineProperty(bound.prototype, 'constructor', {
value: bound,
enumerable: false,
writable: true,
configurable: true
});
}

// Return placeholder that doesn't work
throw new Error("Not implemented");
// Step 5: Return the bound function
return bound;
}

/**
Expand All @@ -44,8 +64,32 @@ function customBind(fn, context, ...boundArgs) {
*/

// Uncomment and implement:
// Function.prototype.customBind = function(context, ...boundArgs) {
// // Your implementation
// };
Function.prototype.customBind = function(context, ...boundArgs) {
const originalFn = this;

if(typeof originalFn !== 'function'){
throw new TypeError('Original function incompatible:'+ typeof originalFn);
}

const bound = function(...args){
const isConstructorCall = this instanceof bound;

const actualContext = isConstructorCall ? this : context;

const argsFn = boundArgs.concat(args);

return originalFn.apply(actualContext, args);
};

if(originalFn.prototype){
function Empty(){}
Empty.prototype = originalFn.prototype;
bound.prototype = new Empty();

bound.prototype.constructor = bound;
}

return bound;
};

module.exports = { customBind };
101 changes: 88 additions & 13 deletions 04-memoization/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,19 +15,69 @@ function memoize(fn, options = {}) {

// Step 1: Extract options with defaults
// const { maxSize, ttl, keyGenerator } = options;
const {maxSize, ttl, keyGenerator} = options;

// Step 2: Create the cache (use Map for ordered keys)
// const cache = new Map();
const cache = new Map();

// Step 3: Create default key generator
// Default: JSON.stringify(args) or args.join(',')
const defaultKeyGenerator = (...args)=>JSON.stringify(args);
const getKey = keyGenerator || defaultKeyGenerator;

// Step 4: Create the memoized function
// - Generate cache key from arguments
// - Check if key exists and is not expired (TTL)
// - If cached, return cached value
// - If not cached, call fn and store result
// - Handle maxSize eviction (remove oldest)
const isExpired = (entry)=>{
if (!ttl || !entry.timestamp) return false;
return Date.now() - entry.timestamp > ttl;
};

const cleanExpired = () =>{
if (!ttl) return;
for (const [key, entry] of cache.entries()) {
if (isExpired(entry)) {
cache.delete(key);
}
}
};

const memoFn = function(...args){
const cacheKey = getKey(args);

if(cache.has(cacheKey)){
const entry = cache.get(cacheKey);

if(!isExpired(entry)){
return entry.value;
}
else{
cache.delete(cacheKey);
}
}

const result = fn.apply(this, args);

cleanExpired();

if (maxSize && cache.size >= maxSize) {
const firstKey = cache.keys().next().value;
cache.delete(firstKey);
}

const entry = {
value: result,
timestamp: ttl ? Date.now() : null
};

cache.set(cacheKey, entry);

return result;
}

// Step 5: Add cache control methods
// memoized.cache = {
Expand All @@ -36,22 +86,47 @@ function memoize(fn, options = {}) {
// has: (key) => cache.has(key),
// get size() { return cache.size; }
// };
memoFn.cache = {
clear: ()=>cache.clear(),
delete: (...args)=>{
const key = getKey(args);
return cache.delete(key);
},
has: (...args)=>{
const key = getKey(args);
if (!cache.has(key)) return false;

const entry = cache.get(key);
if (isExpired(entry)) {
cache.delete(key);
return false;
}

return true;
},
get size(){
cleanExpired();
return cache.size;
}
};


// Step 6: Return memoized function
return memoFn;

// Return placeholder that doesn't work
const memoized = function () {
return undefined;
};
memoized.cache = {
clear: () => {},
delete: () => false,
has: () => false,
get size() {
return -1;
},
};
return memoized;
// // Return placeholder that doesn't work
// const memoized = function () {
// return undefined;
// };
// memoized.cache = {
// clear: () => {},
// delete: () => false,
// has: () => false,
// get size() {
// return -1;
// },
// };
// return memoized;
}

module.exports = { memoize };
Loading