JavaScript Fundamentals
Master arrays, strings, and core concepts. Learn by solving real problems.
0/24
Challenges Completed
0%
Progress
0
Your Total Score
1. URL Query Builder
Building URLs with query parameters is something you'll do constantly when working with APIs. Let's build a utility function that handles all the edge cases.
### Your Task:
- Create a function `buildQueryString(params)` that takes an object
- Return a properly formatted query string starting with `?`
- Use `Object.entries()` to iterate over the object
- Use `encodeURIComponent()` for proper URL encoding
- Filter out `null` or `undefined` values
- Example: `{ name: "Alice", age: 25 }` → `"?name=Alice&age=25"`
2. Config Merger
When building libraries or components, you need to merge user config with defaults. This pattern is everywhere in JavaScript!
### Your Task:
- You have `defaultConfig` and `userConfig` objects
- Create `finalConfig` by merging them (user values override defaults)
- Use the spread operator `{ ...obj1, ...obj2 }`
- Handle nested objects: merge `theme` properties too
- Use nullish coalescing `??` for individual fallbacks
3. Data Transformer Pipeline
Processing data with chained array methods is the bread and butter of JavaScript. Let's build a pipeline that filters, transforms, and aggregates data.
### Your Task:
- Start with an array of `transactions`
- Filter to only `completed` transactions
- Map to extract just `{ id, total }` from each
- Use reduce to calculate the sum of all totals
- Store results in `completedTransactions` and `totalRevenue`
4. Guard Clause Refactoring
Nested if/else blocks make code hard to read. Guard clauses (early returns) flatten your code and make it cleaner. Let's refactor!
### Your Task:
- The `processUser` function has deeply nested conditionals
- Refactor it using guard clauses (early returns)
- Each validation should return early if it fails
- The happy path (valid user) should be at the bottom, not nested
5. Debounce Function
Debouncing delays function execution until a pause in calls. It's essential for search inputs, window resize handlers, and preventing API spam. This is a real utility you'll use!
### Your Task:
- Create a `debounce(fn, delay)` function
- It should return a new function that delays calling `fn`
- If called again before the delay, reset the timer
- Use `setTimeout` and `clearTimeout`
- Use closures to remember the timer ID
6. Safe Object Path Accessor
Accessing deeply nested data safely is a common challenge. Let's build a utility that handles missing properties gracefully.
### Your Task:
- Create a function `get(obj, path, defaultValue)`
- `path` is a string like `"user.profile.name"`
- Navigate through the object following the path
- Return `defaultValue` if any part is missing
- This is similar to lodash's `_.get()`
7. Memoization Cache
Memoization caches function results to avoid expensive recalculations. It's a key performance pattern, especially for recursive or API-heavy functions.
### Your Task:
- Create a `memoize(fn)` function
- It should return a new function that caches results
- Use the arguments as the cache key
- If called with the same args, return cached value
- Track cache hits with a `cacheHits` counter
8. Event Emitter
The publish-subscribe pattern is the foundation of reactive programming. Let's build a simple event emitter from scratch!
### Your Task:
- Create an `EventEmitter` class (or object)
- Implement `on(event, callback)` to subscribe
- Implement `emit(event, data)` to trigger callbacks
- Implement `off(event, callback)` to unsubscribe
- Store listeners in an object/Map
9. Normalize API Response
APIs often return data in inconvenient formats. Let's transform a nested API response into a flat, usable structure.
### Your Task:
- You receive `apiResponse` with nested user data
- Create `normalizedUsers` array with flat objects
- Each object should have: `id`, `fullName` (combined first + last), `email`, `city`
- Handle missing `address` gracefully (use "Unknown" for city)
10. Remove Duplicates
Deduplicating data is a constant need. Let's explore multiple ways to remove duplicates from arrays.
### Your Task:
- Create `uniqueNumbers` from `numbers` (remove duplicates)
- Create `uniqueEmails` from `users` (unique by email property)
- Create `uniqueUsers` keeping only first occurrence of each email
- Use Set, filter with indexOf, or reduce
11. Group By
Grouping data by a property is one of the most useful patterns. Let's build our own `groupBy` function!
### Your Task:
- Create a `groupBy(array, key)` function
- It should return an object where keys are unique values of `key`
- Each key maps to an array of matching items
- Test it by grouping `products` by `category`
12. Multi-Criteria Sort
Real sorting needs often involve multiple criteria. Let's sort users by status (active first), then by name alphabetically.
### Your Task:
- Sort `users` array with multiple criteria
- Primary: `isActive` users first (true before false)
- Secondary: Alphabetically by `name`
- Don't mutate the original array (create a copy first)
13. Flatten Nested Arrays
Working with nested data structures often requires flattening. Let's build a function that handles any depth!
### Your Task:
- Create a `flatten(array, depth)` function
- It should flatten nested arrays up to `depth` levels
- `depth = 1` flattens one level, `depth = Infinity` flattens completely
- Use recursion or the built-in `Array.flat()` for comparison
14. Pagination Helper
Every list view needs pagination. Let's build a reusable pagination utility that handles all the edge cases.
### Your Task:
- Create a `paginate(array, page, pageSize)` function
- Return an object with: `data` (items for current page), `page`, `pageSize`, `totalItems`, `totalPages`, `hasNext`, `hasPrev`
- Handle edge cases: invalid page numbers, empty arrays
15. Factory Functions
Factory functions create objects without the `new` keyword. They're more flexible than classes and enable private state through closures.
### Your Task:
- Create a `createUser(name, email)` factory function
- It should return an object with `name`, `email`, and methods
- Add a `getInfo()` method that returns a formatted string
- Add an `updateEmail(newEmail)` method
- Keep the original email accessible only through `getInfo()`
16. Composition Over Inheritance
Instead of complex inheritance hierarchies, compose objects from smaller, focused functions. This is the modern way to share behavior!
### Your Task:
- Create behavior functions: `canSwim()`, `canFly()`, `canWalk()`
- Each returns an object with a method (e.g., `swim()`)
- Create a `duck` by composing all three behaviors
- Create a `fish` with only swim
- Use object spread to merge behaviors
17. Module Pattern (Private State)
The module pattern uses closures to create private variables that can't be accessed from outside. It's how we had encapsulation before ES6 classes!
### Your Task:
- Create a `createCounter()` function (no args)
- Return an object with: `increment()`, `decrement()`, `getCount()`, `reset()`
- The count itself should be PRIVATE (not accessible directly)
- `getCount()` is the only way to read the value
18. Method Chaining (Fluent API)
Method chaining allows calling multiple methods in sequence: `obj.method1().method2().method3()`. The secret? Return `this` from each method!
### Your Task:
- Create a `QueryBuilder` class (or factory)
- Methods: `select(fields)`, `from(table)`, `where(condition)`, `build()`
- Each method (except `build`) should return `this` for chaining
- `build()` returns the SQL string
19. Custom Iterator
Iterators let you define how objects are looped over with `for...of`. Understanding this unlocks powerful patterns like generators and lazy evaluation.
### Your Task:
- Create a `Range` class that represents a number range
- Implement `Symbol.iterator` so it works with `for...of`
- `new Range(1, 5)` should iterate: 1, 2, 3, 4, 5
- Also implement `toArray()` method for convenience
20. Sequential API Calls
Often you need to make API calls in sequence where each depends on the previous result. Let's fetch user data, then their posts.
### Your Task:
- `fetchUser(id)` and `fetchPosts(userId)` are provided
- Create an `async` function `getUserWithPosts(id)`
- First await the user, then await their posts
- Return an object: `{ user, posts }`
- Handle the async flow properly with async/await
21. Parallel with Promise.all
When requests don't depend on each other, run them in parallel for better performance! Let's fetch multiple users at once.
### Your Task:
- `fetchUser(id)` returns a promise for one user
- Create `fetchAllUsers(ids)` that fetches all users in parallel
- Use `Promise.all()` to wait for all requests
- Return the array of users
- Compare timing: parallel is faster than sequential!
22. Request with Timeout
APIs can be slow or hang forever. Let's implement a timeout that cancels the request if it takes too long using `Promise.race()`.
### Your Task:
- Create a `withTimeout(promise, ms)` function
- It races the original promise against a timeout
- If timeout wins, reject with `"Request timed out"`
- If original promise wins, return its result
- Use `Promise.race()` for the racing logic
23. Retry Logic with Backoff
Network requests can fail. Good code retries with exponential backoff: waiting longer between each attempt. This is production-grade resilience!
### Your Task:
- Create `retryWithBackoff(fn, retries, delay)`
- `fn` is an async function that might fail
- Retry up to `retries` times
- Wait `delay` ms, then `delay * 2`, then `delay * 4`...
- Return result on success, throw after all retries fail
24. Loading State Machine
Any async operation has states: idle, loading, success, or error. Managing these states properly is crucial for good UX. Let's build a state machine!
### Your Task:
- Create a `createAsyncState()` factory
- It should track: `status` (idle/loading/success/error), `data`, `error`
- Methods: `startLoading()`, `setSuccess(data)`, `setError(error)`, `reset()`
- Create `getState()` to return current state object
Ready to Code?
Start with the first challenge and work your way up to become a coding master!
Start First Challenge