diff --git a/lib/problems/javascript.ts b/lib/problems/javascript.ts
index a358131..19f5be2 100644
--- a/lib/problems/javascript.ts
+++ b/lib/problems/javascript.ts
@@ -2883,7 +2883,7 @@ const setY = new Set([4, 5, 6, 7, 8]);`,
sample: '[...new Set([...[...setX].filter(x => !setY.has(x)), ...[...setY].filter(x => !setX.has(x))])]',
hints: ['Find elements in X not in Y', 'Find elements in Y not in X', 'Combine both results'],
validPatterns: [
- /\[\s*\.\.\.\[\s*\.\.\.setX\s*\]\.filter[^]]*\.\.\.\[\s*\.\.\.setY\s*\]\.filter/,
+ /\[\s*\.\.\.\[\s*\.\.\.setX\s*\]\.filter[\s\S]*\.\.\.\[\s*\.\.\.setY\s*\]\.filter/,
/filter\s*\(\s*\w+\s*=>\s*!set[XY]\.has\s*\(\s*\w+\s*\)\s*\)/,
],
tags: ['Set', 'symmetric-difference', 'filter', 'has'],
@@ -3798,6 +3798,7094 @@ const logs = [];`,
],
tags: ['Proxy', 'Reflect', 'logging'],
},
+ // ========================================
+ // REGULAR EXPRESSIONS & STRING PARSING
+ // ========================================
+ {
+ id: 'js-regex-001',
+ category: 'Regular Expressions',
+ difficulty: 'easy',
+ title: 'Test for Digits',
+ text: 'Use a regex to test if the string contains any digit. Return true or false.',
+ setup: 'const str = "Hello World 123";',
+ setupCode: 'const str = "Hello World 123";',
+ expected: true,
+ sample: '/\\d/.test(str)',
+ hints: ['Use \\d to match digits', 'The .test() method returns a boolean'],
+ tags: ['regex', 'test', 'digits'],
+ },
+ {
+ id: 'js-regex-002',
+ category: 'Regular Expressions',
+ difficulty: 'easy',
+ title: 'Match All Digits',
+ text: 'Use a regex to find all digits in the string. Return an array of matches.',
+ setup: 'const str = "abc123def456";',
+ setupCode: 'const str = "abc123def456";',
+ expected: ['1', '2', '3', '4', '5', '6'],
+ sample: 'str.match(/\\d/g)',
+ hints: ['Use the g flag for global matching', 'Use \\d to match digits'],
+ tags: ['regex', 'match', 'global-flag'],
+ },
+ {
+ id: 'js-regex-003',
+ category: 'Regular Expressions',
+ difficulty: 'easy',
+ title: 'Case Insensitive Match',
+ text: 'Test if the string contains "hello" regardless of case.',
+ setup: 'const str = "HELLO World";',
+ setupCode: 'const str = "HELLO World";',
+ expected: true,
+ sample: '/hello/i.test(str)',
+ hints: ['Use the i flag for case insensitive matching', 'The pattern should be lowercase'],
+ tags: ['regex', 'test', 'case-insensitive'],
+ },
+ {
+ id: 'js-regex-004',
+ category: 'Regular Expressions',
+ difficulty: 'easy',
+ title: 'Match Word Characters',
+ text: 'Find all word characters (letters, digits, underscore) in the string.',
+ setup: 'const str = "Hi! @user_123";',
+ setupCode: 'const str = "Hi! @user_123";',
+ expected: ['H', 'i', 'u', 's', 'e', 'r', '_', '1', '2', '3'],
+ sample: 'str.match(/\\w/g)',
+ hints: ['Use \\w to match word characters', 'Word characters include a-z, A-Z, 0-9, and _'],
+ tags: ['regex', 'match', 'word-characters'],
+ },
+ {
+ id: 'js-regex-005',
+ category: 'Regular Expressions',
+ difficulty: 'easy',
+ title: 'Match Whitespace',
+ text: 'Count the number of whitespace characters in the string.',
+ setup: 'const str = "Hello World Test";',
+ setupCode: 'const str = "Hello World Test";',
+ expected: 3,
+ sample: '(str.match(/\\s/g) || []).length',
+ hints: ['Use \\s to match whitespace', 'Handle the case where match returns null'],
+ tags: ['regex', 'match', 'whitespace'],
+ },
+ {
+ id: 'js-regex-006',
+ category: 'Regular Expressions',
+ difficulty: 'easy',
+ title: 'Start of String Match',
+ text: 'Test if the string starts with "Hello".',
+ setup: 'const str = "Hello World";',
+ setupCode: 'const str = "Hello World";',
+ expected: true,
+ sample: '/^Hello/.test(str)',
+ hints: ['Use ^ to match the start of a string', 'The ^ anchor asserts position at start'],
+ tags: ['regex', 'test', 'anchor'],
+ },
+ {
+ id: 'js-regex-007',
+ category: 'Regular Expressions',
+ difficulty: 'easy',
+ title: 'End of String Match',
+ text: 'Test if the string ends with a digit.',
+ setup: 'const str = "Order #123";',
+ setupCode: 'const str = "Order #123";',
+ expected: true,
+ sample: '/\\d$/.test(str)',
+ hints: ['Use $ to match the end of a string', 'Combine with \\d to match a digit at the end'],
+ tags: ['regex', 'test', 'anchor'],
+ },
+ {
+ id: 'js-regex-008',
+ category: 'Regular Expressions',
+ difficulty: 'easy',
+ title: 'Match One or More',
+ text: 'Find sequences of one or more digits in the string.',
+ setup: 'const str = "abc123def45ghi6";',
+ setupCode: 'const str = "abc123def45ghi6";',
+ expected: ['123', '45', '6'],
+ sample: 'str.match(/\\d+/g)',
+ hints: ['Use + quantifier for one or more', 'Use g flag to find all matches'],
+ tags: ['regex', 'match', 'quantifier'],
+ },
+ {
+ id: 'js-regex-009',
+ category: 'Regular Expressions',
+ difficulty: 'easy',
+ title: 'Match Zero or More',
+ text: 'Match "go" followed by zero or more "o" characters. Find all matches.',
+ setup: 'const str = "go goo gooo goooo";',
+ setupCode: 'const str = "go goo gooo goooo";',
+ expected: ['go', 'goo', 'gooo', 'goooo'],
+ sample: 'str.match(/goo*/g)',
+ hints: ['Use * quantifier for zero or more', 'The pattern goo* matches g followed by one or more o'],
+ tags: ['regex', 'match', 'quantifier'],
+ },
+ {
+ id: 'js-regex-010',
+ category: 'Regular Expressions',
+ difficulty: 'easy',
+ title: 'Optional Character',
+ text: 'Match both "color" and "colour" in the string.',
+ setup: 'const str = "color and colour are both valid";',
+ setupCode: 'const str = "color and colour are both valid";',
+ expected: ['color', 'colour'],
+ sample: 'str.match(/colou?r/g)',
+ hints: ['Use ? for optional character', 'The u is optional in the pattern'],
+ tags: ['regex', 'match', 'quantifier'],
+ },
+ {
+ id: 'js-regex-011',
+ category: 'Regular Expressions',
+ difficulty: 'easy',
+ title: 'Character Set',
+ text: 'Match all vowels in the string (case insensitive).',
+ setup: 'const str = "Hello World";',
+ setupCode: 'const str = "Hello World";',
+ expected: ['e', 'o', 'o'],
+ sample: 'str.match(/[aeiou]/gi)',
+ hints: ['Use [] for character set', 'Use i flag for case insensitive'],
+ tags: ['regex', 'match', 'character-class'],
+ },
+ {
+ id: 'js-regex-012',
+ category: 'Regular Expressions',
+ difficulty: 'easy',
+ title: 'Negated Character Set',
+ text: 'Match all non-vowel characters (consonants and others).',
+ setup: 'const str = "Hello";',
+ setupCode: 'const str = "Hello";',
+ expected: ['H', 'l', 'l'],
+ sample: 'str.match(/[^aeiou]/gi)',
+ hints: ['Use [^] for negated set', 'This matches anything NOT in the set'],
+ tags: ['regex', 'match', 'character-class'],
+ },
+ {
+ id: 'js-regex-013',
+ category: 'Regular Expressions',
+ difficulty: 'easy',
+ title: 'Range in Character Set',
+ text: 'Match all lowercase letters from a to m.',
+ setup: 'const str = "programming";',
+ setupCode: 'const str = "programming";',
+ expected: ['g', 'a', 'm', 'm', 'i', 'g'],
+ sample: 'str.match(/[a-m]/g)',
+ hints: ['Use - to define a range in character set', '[a-m] matches letters a through m'],
+ tags: ['regex', 'match', 'character-class'],
+ },
+ {
+ id: 'js-regex-014',
+ category: 'Regular Expressions',
+ difficulty: 'easy',
+ title: 'Dot Wildcard',
+ text: 'Match any 3-character pattern that starts with "c" and ends with "t".',
+ setup: 'const str = "cat cot cut cart";',
+ setupCode: 'const str = "cat cot cut cart";',
+ expected: ['cat', 'cot', 'cut'],
+ sample: 'str.match(/c.t/g)',
+ hints: ['Use . to match any character', 'The dot matches exactly one character'],
+ tags: ['regex', 'match', 'wildcard'],
+ },
+ {
+ id: 'js-regex-015',
+ category: 'Regular Expressions',
+ difficulty: 'easy',
+ title: 'Escape Special Character',
+ text: 'Test if the string contains a literal dot.',
+ setup: 'const str = "file.txt";',
+ setupCode: 'const str = "file.txt";',
+ expected: true,
+ sample: '/\\./.test(str)',
+ hints: ['Escape the dot with backslash', '\\. matches a literal dot'],
+ tags: ['regex', 'test', 'escape'],
+ },
+ {
+ id: 'js-regex-016',
+ category: 'Regular Expressions',
+ difficulty: 'easy',
+ title: 'Word Boundary',
+ text: 'Test if the word "cat" exists as a whole word (not part of another word).',
+ setup: 'const str = "I have a cat and a caterpillar";',
+ setupCode: 'const str = "I have a cat and a caterpillar";',
+ expected: true,
+ sample: '/\\bcat\\b/.test(str)',
+ hints: ['Use \\b for word boundary', 'Word boundary matches between word and non-word characters'],
+ tags: ['regex', 'test', 'boundary'],
+ },
+ {
+ id: 'js-regex-017',
+ category: 'String Parsing',
+ difficulty: 'easy',
+ title: 'Split on Whitespace',
+ text: 'Split the string into words using regex to handle multiple spaces.',
+ setup: 'const str = "Hello World Test";',
+ setupCode: 'const str = "Hello World Test";',
+ expected: ['Hello', 'World', 'Test'],
+ sample: 'str.split(/\\s+/)',
+ hints: ['Use \\s+ to match one or more whitespace', 'split() can take a regex'],
+ tags: ['regex', 'split', 'whitespace'],
+ },
+ {
+ id: 'js-regex-018',
+ category: 'String Parsing',
+ difficulty: 'easy',
+ title: 'Replace All Digits',
+ text: 'Replace all digits in the string with "X".',
+ setup: 'const str = "Phone: 123-456-7890";',
+ setupCode: 'const str = "Phone: 123-456-7890";',
+ expected: 'Phone: XXX-XXX-XXXX',
+ sample: 'str.replace(/\\d/g, "X")',
+ hints: ['Use replace with g flag', 'Replace each digit individually'],
+ tags: ['regex', 'replace', 'global'],
+ },
+ {
+ id: 'js-regex-019',
+ category: 'Regular Expressions',
+ difficulty: 'easy',
+ title: 'Match Exact Count',
+ text: 'Match exactly 3 consecutive digits.',
+ setup: 'const str = "12 123 1234 12345";',
+ setupCode: 'const str = "12 123 1234 12345";',
+ expected: ['123', '123', '123'],
+ sample: 'str.match(/\\d{3}/g)',
+ hints: ['Use {n} for exact count', '{3} means exactly 3 occurrences'],
+ tags: ['regex', 'match', 'quantifier'],
+ },
+ {
+ id: 'js-regex-020',
+ category: 'Regular Expressions',
+ difficulty: 'easy',
+ title: 'Match Range Count',
+ text: 'Match sequences of 2 to 4 digits.',
+ setup: 'const str = "1 12 123 1234 12345";',
+ setupCode: 'const str = "1 12 123 1234 12345";',
+ expected: ['12', '123', '1234', '1234'],
+ sample: 'str.match(/\\d{2,4}/g)',
+ hints: ['Use {n,m} for range', '{2,4} means 2 to 4 occurrences'],
+ tags: ['regex', 'match', 'quantifier'],
+ },
+ {
+ id: 'js-regex-021',
+ category: 'String Parsing',
+ difficulty: 'easy',
+ title: 'Extract File Extension',
+ text: 'Extract the file extension from the filename.',
+ setup: 'const filename = "document.pdf";',
+ setupCode: 'const filename = "document.pdf";',
+ expected: 'pdf',
+ sample: 'filename.match(/\\.(\\w+)$/)[1]',
+ hints: ['Match dot followed by word characters at end', 'Use capturing group for extension'],
+ tags: ['regex', 'match', 'parsing'],
+ },
+ {
+ id: 'js-regex-022',
+ category: 'Regular Expressions',
+ difficulty: 'easy',
+ title: 'Non-Digit Characters',
+ text: 'Find all non-digit characters in the string.',
+ setup: 'const str = "abc123";',
+ setupCode: 'const str = "abc123";',
+ expected: ['a', 'b', 'c'],
+ sample: 'str.match(/\\D/g)',
+ hints: ['Use \\D to match non-digits', 'Capital D is the negation of \\d'],
+ tags: ['regex', 'match', 'character-class'],
+ },
+ {
+ id: 'js-regex-023',
+ category: 'Regular Expressions',
+ difficulty: 'easy',
+ title: 'Non-Word Characters',
+ text: 'Find all non-word characters (special chars, spaces).',
+ setup: 'const str = "Hi! How are you?";',
+ setupCode: 'const str = "Hi! How are you?";',
+ expected: ['!', ' ', ' ', ' ', '?'],
+ sample: 'str.match(/\\W/g)',
+ hints: ['Use \\W to match non-word characters', 'Includes spaces and punctuation'],
+ tags: ['regex', 'match', 'character-class'],
+ },
+ {
+ id: 'js-regex-024',
+ category: 'Regular Expressions',
+ difficulty: 'easy',
+ title: 'Match Words',
+ text: 'Find all words (sequences of word characters) in the string.',
+ setup: 'const str = "Hello, World! How are you?";',
+ setupCode: 'const str = "Hello, World! How are you?";',
+ expected: ['Hello', 'World', 'How', 'are', 'you'],
+ sample: 'str.match(/\\w+/g)',
+ hints: ['Use \\w+ to match sequences of word characters', 'This effectively extracts words'],
+ tags: ['regex', 'match', 'words'],
+ },
+ {
+ id: 'js-regex-025',
+ category: 'String Parsing',
+ difficulty: 'easy',
+ title: 'Remove Extra Spaces',
+ text: 'Replace multiple consecutive spaces with a single space.',
+ setup: 'const str = "Hello World Test";',
+ setupCode: 'const str = "Hello World Test";',
+ expected: 'Hello World Test',
+ sample: 'str.replace(/\\s+/g, " ")',
+ hints: ['Use \\s+ to match multiple spaces', 'Replace with single space'],
+ tags: ['regex', 'replace', 'whitespace'],
+ },
+ {
+ id: 'js-regex-026',
+ category: 'Regular Expressions',
+ difficulty: 'medium',
+ title: 'Basic Capturing Group',
+ text: 'Extract the area code from the phone number using capturing groups.',
+ setup: 'const phone = "(555) 123-4567";',
+ setupCode: 'const phone = "(555) 123-4567";',
+ expected: '555',
+ sample: 'phone.match(/\\((\\d{3})\\)/)[1]',
+ hints: ['Use () to create a capturing group', 'Access the group with [1]'],
+ tags: ['regex', 'match', 'capturing-group'],
+ },
+ {
+ id: 'js-regex-027',
+ category: 'Regular Expressions',
+ difficulty: 'medium',
+ title: 'Multiple Capturing Groups',
+ text: 'Extract month, day, and year from the date string.',
+ setup: 'const date = "12/25/2023";',
+ setupCode: 'const date = "12/25/2023";',
+ expected: ['12/25/2023', '12', '25', '2023'],
+ sample: 'date.match(/(\\d{2})\\/(\\d{2})\\/(\\d{4})/)',
+ hints: ['Each () creates a separate group', 'Groups are numbered left to right'],
+ tags: ['regex', 'match', 'capturing-group'],
+ },
+ {
+ id: 'js-regex-028',
+ category: 'Regular Expressions',
+ difficulty: 'medium',
+ title: 'Non-Capturing Group',
+ text: 'Match repeated "ab" patterns but do not capture the group. Count the matches.',
+ setup: 'const str = "ab ab ab abc abab";',
+ setupCode: 'const str = "ab ab ab abc abab";',
+ expected: 5,
+ sample: '(str.match(/(?:ab)/g) || []).length',
+ hints: ['Use (?:) for non-capturing group', 'Non-capturing groups match but do not remember'],
+ tags: ['regex', 'match', 'non-capturing'],
+ },
+ {
+ id: 'js-regex-029',
+ category: 'Regular Expressions',
+ difficulty: 'medium',
+ title: 'Named Capturing Group',
+ text: 'Extract the username from an email using named capturing group.',
+ setup: 'const email = "john.doe@example.com";',
+ setupCode: 'const email = "john.doe@example.com";',
+ expected: 'john.doe',
+ sample: 'email.match(/(?
Hello World
";', + expected: 'Hello World', + sample: 'html.replace(/<[^>]*>/g, "")', + hints: ['Match < followed by non-> chars and >', '[^>]* matches any char except >'], + tags: ['regex', 'replace', 'html'], + }, + { + id: 'js-regex-048', + category: 'String Parsing', + difficulty: 'medium', + title: 'CamelCase to Kebab', + text: 'Convert camelCase string to kebab-case.', + setup: 'const str = "backgroundColor";', + setupCode: 'const str = "backgroundColor";', + expected: 'background-color', + sample: 'str.replace(/([A-Z])/g, "-$1").toLowerCase()', + hints: ['Find uppercase letters and prepend hyphen', 'Convert result to lowercase'], + tags: ['regex', 'replace', 'case-conversion'], + }, + { + id: 'js-regex-049', + category: 'String Parsing', + difficulty: 'medium', + title: 'Validate Username', + text: 'Test if username is valid (3-16 chars, alphanumeric and underscores only, starts with letter).', + setup: 'const username = "john_doe123";', + setupCode: 'const username = "john_doe123";', + expected: true, + sample: '/^[a-zA-Z]\\w{2,15}$/.test(username)', + hints: ['Start with letter, then word chars', '{2,15} gives total 3-16 characters'], + tags: ['regex', 'test', 'validation'], + }, + { + id: 'js-regex-050', + category: 'String Parsing', + difficulty: 'medium', + title: 'Format Phone Number', + text: 'Format the 10-digit number as (XXX) XXX-XXXX.', + setup: 'const phone = "5551234567";', + setupCode: 'const phone = "5551234567";', + expected: '(555) 123-4567', + sample: 'phone.replace(/(\\d{3})(\\d{3})(\\d{4})/, "($1) $2-$3")', + hints: ['Capture three groups of digits', 'Use $1, $2, $3 in replacement'], + tags: ['regex', 'replace', 'formatting'], + }, + { + id: 'js-regex-051', + category: 'Regular Expressions', + difficulty: 'medium', + title: 'Match IP Octets', + text: 'Extract all octets from the IP address.', + setup: 'const ip = "192.168.1.100";', + setupCode: 'const ip = "192.168.1.100";', + expected: ['192', '168', '1', '100'], + sample: 'ip.match(/\\d+/g)', + hints: ['Match sequences of digits', 'Dots act as separators'], + tags: ['regex', 'match', 'ip-address'], + }, + { + id: 'js-regex-052', + category: 'String Parsing', + difficulty: 'medium', + title: 'Extract Domain', + text: 'Extract the domain name from the URL.', + setup: 'const url = "https://www.example.com/path/page";', + setupCode: 'const url = "https://www.example.com/path/page";', + expected: 'www.example.com', + sample: 'url.match(/https?:\\/\\/([^\\/]+)/)[1]', + hints: ['Match protocol then capture until slash', '[^\\/]+ matches non-slash characters'], + tags: ['regex', 'match', 'url'], + }, + { + id: 'js-regex-053', + category: 'String Parsing', + difficulty: 'medium', + title: 'Mask Credit Card', + text: 'Mask all but the last 4 digits of the credit card.', + setup: 'const card = "4532015112830366";', + setupCode: 'const card = "4532015112830366";', + expected: '************0366', + sample: 'card.replace(/\\d(?=\\d{4})/g, "*")', + hints: ['Use lookahead to keep last 4 digits', 'Replace each digit that has 4+ digits after it'], + tags: ['regex', 'replace', 'masking'], + }, + { + id: 'js-regex-054', + category: 'Regular Expressions', + difficulty: 'medium', + title: 'Match HEX Color', + text: 'Find all valid hex color codes in the string.', + setup: 'const css = "color: #fff; background: #abc123; border: #12G456";', + setupCode: 'const css = "color: #fff; background: #abc123; border: #12G456";', + expected: ['#fff', '#abc123'], + sample: 'css.match(/#[0-9a-fA-F]{3}(?:[0-9a-fA-F]{3})?\\b/g)', + hints: ['Match # followed by 3 or 6 hex digits', 'Use word boundary to avoid partial matches'], + tags: ['regex', 'match', 'color'], + }, + { + id: 'js-regex-055', + category: 'String Parsing', + difficulty: 'medium', + title: 'Parse CSV Line', + text: 'Split a CSV line handling quoted values with commas.', + setup: 'const csv = \'John,"Doe, Jr.",30\';', + setupCode: 'const csv = \'John,"Doe, Jr.",30\';', + expected: ['John', '"Doe, Jr."', '30'], + sample: 'csv.match(/("[^"]*"|[^,]+)/g)', + hints: ['Match quoted strings OR non-comma sequences', 'Use alternation with |'], + tags: ['regex', 'match', 'csv'], + }, + { + id: 'js-regex-056', + category: 'Regular Expressions', + difficulty: 'hard', + title: 'Email Validation', + text: 'Test if the string is a valid email format.', + setup: 'const email = "test.user+tag@sub.example.com";', + setupCode: 'const email = "test.user+tag@sub.example.com";', + expected: true, + sample: '/^[\\w.+-]+@[\\w.-]+\\.[a-zA-Z]{2,}$/.test(email)', + hints: ['Match username @ domain . tld', 'Allow dots, plus, hyphen in username'], + tags: ['regex', 'test', 'email', 'validation'], + }, + { + id: 'js-regex-057', + category: 'Regular Expressions', + difficulty: 'hard', + title: 'URL Validation', + text: 'Test if the string is a valid URL format.', + setup: 'const url = "https://www.example.com:8080/path?query=1#hash";', + setupCode: 'const url = "https://www.example.com:8080/path?query=1#hash";', + expected: true, + sample: '/^https?:\\/\\/[\\w.-]+(:\\d+)?(\\/[\\w./-]*)?(\\?[\\w=&]*)?(#\\w*)?$/.test(url)', + hints: ['Match protocol, domain, optional port, path, query, hash', 'Each part is optional except protocol and domain'], + tags: ['regex', 'test', 'url', 'validation'], + }, + { + id: 'js-regex-058', + category: 'Regular Expressions', + difficulty: 'hard', + title: 'Password Strength', + text: 'Test if password has at least 8 chars, one uppercase, one lowercase, one digit, and one special char.', + setup: 'const password = "MyP@ssw0rd";', + setupCode: 'const password = "MyP@ssw0rd";', + expected: true, + sample: '/^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*[!@#$%^&*]).{8,}$/.test(password)', + hints: ['Use multiple lookaheads for each requirement', 'Lookaheads check without consuming'], + tags: ['regex', 'test', 'password', 'validation'], + }, + { + id: 'js-regex-059', + category: 'Regular Expressions', + difficulty: 'hard', + title: 'Phone Number Formats', + text: 'Match phone numbers in various formats: (555) 123-4567, 555-123-4567, 5551234567.', + setup: 'const text = "Call (555) 123-4567 or 555-123-4567 or 5551234567";', + setupCode: 'const text = "Call (555) 123-4567 or 555-123-4567 or 5551234567";', + expected: ['(555) 123-4567', '555-123-4567', '5551234567'], + sample: 'text.match(/\\(?\\d{3}\\)?[-.\\s]?\\d{3}[-.\\s]?\\d{4}/g)', + hints: ['Make parens and separators optional', 'Use [-.\\s]? for flexible separators'], + tags: ['regex', 'match', 'phone', 'validation'], + }, + { + id: 'js-regex-060', + category: 'String Parsing', + difficulty: 'hard', + title: 'Extract All URLs', + text: 'Extract all URLs from the text.', + setup: 'const text = "Visit https://example.com and http://test.org/path for more info.";', + setupCode: 'const text = "Visit https://example.com and http://test.org/path for more info.";', + expected: ['https://example.com', 'http://test.org/path'], + sample: 'text.match(/https?:\\/\\/[\\w.-]+(\\/[\\w./-]*)?/g)', + hints: ['Match http:// or https:// followed by domain and optional path', 'Use optional group for path'], + tags: ['regex', 'match', 'url', 'extraction'], + }, + { + id: 'js-regex-061', + category: 'String Parsing', + difficulty: 'hard', + title: 'Parse HTML Attributes', + text: 'Extract all attribute name-value pairs from the HTML tag.', + setup: 'const tag = \'\';', + setupCode: 'const tag = \'\';', + expected: ['type="text"', 'name="email"'], + sample: 'tag.match(/\\w+="[^"]*"/g)', + hints: ['Match word="value" pattern', 'Value is anything except quote'], + tags: ['regex', 'match', 'html', 'parsing'], + }, + { + id: 'js-regex-062', + category: 'String Parsing', + difficulty: 'hard', + title: 'Tokenize Expression', + text: 'Split a math expression into tokens (numbers and operators).', + setup: 'const expr = "12+34*56-78/90";', + setupCode: 'const expr = "12+34*56-78/90";', + expected: ['12', '+', '34', '*', '56', '-', '78', '/', '90'], + sample: 'expr.match(/\\d+|[+\\-*/]/g)', + hints: ['Match numbers OR operators', 'Use alternation with |'], + tags: ['regex', 'match', 'tokenize', 'parsing'], + }, + { + id: 'js-regex-063', + category: 'Regular Expressions', + difficulty: 'hard', + title: 'Validate Date Format', + text: 'Test if the string is a valid date in YYYY-MM-DD format.', + setup: 'const date = "2023-12-25";', + setupCode: 'const date = "2023-12-25";', + expected: true, + sample: '/^\\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\\d|3[01])$/.test(date)', + hints: ['Validate year, month 01-12, day 01-31', 'Use alternation for valid ranges'], + tags: ['regex', 'test', 'date', 'validation'], + }, + { + id: 'js-regex-064', + category: 'String Parsing', + difficulty: 'hard', + title: 'Convert Date Format', + text: 'Convert date from MM/DD/YYYY to YYYY-MM-DD format.', + setup: 'const date = "12/25/2023";', + setupCode: 'const date = "12/25/2023";', + expected: '2023-12-25', + sample: 'date.replace(/(\\d{2})\\/(\\d{2})\\/(\\d{4})/, "$3-$1-$2")', + hints: ['Capture month, day, year separately', 'Rearrange with $3-$1-$2'], + tags: ['regex', 'replace', 'date', 'formatting'], + }, + { + id: 'js-regex-065', + category: 'Regular Expressions', + difficulty: 'hard', + title: 'Match Balanced Parens (Simple)', + text: 'Extract content inside the outermost parentheses.', + setup: 'const str = "func(arg1, (nested), arg2)";', + setupCode: 'const str = "func(arg1, (nested), arg2)";', + expected: 'arg1, (nested), arg2', + sample: 'str.match(/\\((.*)\\)/)[1]', + hints: ['Match from first ( to last )', 'Greedy .* captures everything between'], + tags: ['regex', 'match', 'parsing'], + }, + { + id: 'js-regex-066', + category: 'String Parsing', + difficulty: 'hard', + title: 'Sentence Case', + text: 'Convert text to sentence case (capitalize first letter of each sentence).', + setup: 'const text = "hello world. how are you? i am fine.";', + setupCode: 'const text = "hello world. how are you? i am fine.";', + expected: 'Hello world. How are you? I am fine.', + sample: 'text.replace(/(^|[.!?]\\s+)([a-z])/g, (m, p1, p2) => p1 + p2.toUpperCase())', + hints: ['Match start or punctuation followed by letter', 'Use callback to uppercase'], + tags: ['regex', 'replace', 'case-conversion'], + }, + { + id: 'js-regex-067', + category: 'String Parsing', + difficulty: 'hard', + title: 'Parse JSON Path', + text: 'Extract all keys from a JSON path string.', + setup: 'const path = "user.profile.address.city";', + setupCode: 'const path = "user.profile.address.city";', + expected: ['user', 'profile', 'address', 'city'], + sample: 'path.match(/[^.]+/g)', + hints: ['Match sequences of non-dot characters', 'Or simply split by dot'], + tags: ['regex', 'match', 'json', 'parsing'], + }, + { + id: 'js-regex-068', + category: 'Regular Expressions', + difficulty: 'hard', + title: 'Validate IPv4', + text: 'Test if the string is a valid IPv4 address.', + setup: 'const ip = "192.168.1.255";', + setupCode: 'const ip = "192.168.1.255";', + expected: true, + sample: '/^((25[0-5]|2[0-4]\\d|[01]?\\d\\d?)\\.){3}(25[0-5]|2[0-4]\\d|[01]?\\d\\d?)$/.test(ip)', + hints: ['Each octet is 0-255', 'Use alternation for different ranges'], + tags: ['regex', 'test', 'ip', 'validation'], + }, + { + id: 'js-regex-069', + category: 'String Parsing', + difficulty: 'hard', + title: 'Extract CSS Properties', + text: 'Extract property-value pairs from inline CSS.', + setup: 'const style = "color: red; font-size: 14px; margin: 10px 5px;";', + setupCode: 'const style = "color: red; font-size: 14px; margin: 10px 5px;";', + expected: ['color: red', 'font-size: 14px', 'margin: 10px 5px'], + sample: 'style.match(/[\\w-]+:\\s*[^;]+/g)', + hints: ['Match property: value patterns', 'Value continues until semicolon'], + tags: ['regex', 'match', 'css', 'parsing'], + }, + { + id: 'js-regex-070', + category: 'Regular Expressions', + difficulty: 'hard', + title: 'Validate Credit Card', + text: 'Test if the string looks like a valid credit card number (13-19 digits, optionally grouped by spaces or dashes).', + setup: 'const card = "4532-0151-1283-0366";', + setupCode: 'const card = "4532-0151-1283-0366";', + expected: true, + sample: '/^[\\d]{13,19}$|^([\\d]{4}[- ]?){3,4}[\\d]{1,4}$/.test(card)', + hints: ['Allow plain digits or grouped format', 'Groups of 4 with optional separator'], + tags: ['regex', 'test', 'credit-card', 'validation'], + }, + { + id: 'js-regex-071', + category: 'String Parsing', + difficulty: 'hard', + title: 'Slugify String', + text: 'Convert a title to a URL-friendly slug (lowercase, spaces to hyphens, remove special chars).', + setup: 'const title = "Hello World! How Are You?";', + setupCode: 'const title = "Hello World! How Are You?";', + expected: 'hello-world-how-are-you', + sample: 'title.toLowerCase().replace(/[^\\w\\s-]/g, "").replace(/\\s+/g, "-")', + hints: ['First remove special chars', 'Then replace spaces with hyphens'], + tags: ['regex', 'replace', 'slug', 'formatting'], + }, + { + id: 'js-regex-072', + category: 'Regular Expressions', + difficulty: 'hard', + title: 'Match Template Literals', + text: 'Extract all template variable names from a template string (format: {{varName}}).', + setup: 'const template = "Hello {{name}}, your order {{orderId}} is ready!";', + setupCode: 'const template = "Hello {{name}}, your order {{orderId}} is ready!";', + expected: ['name', 'orderId'], + sample: 'template.match(/\\{\\{(\\w+)\\}\\}/g).map(m => m.slice(2, -2))', + hints: ['Match {{word}} pattern', 'Extract just the variable name'], + tags: ['regex', 'match', 'template', 'parsing'], + }, + { + id: 'js-regex-073', + category: 'String Parsing', + difficulty: 'hard', + title: 'Highlight Search Terms', + text: 'Wrap all occurrences of search term in tags (case insensitive).', + setup: 'const text = "JavaScript is great. I love javascript!";', + setupCode: 'const text = "JavaScript is great. I love javascript!";', + expected: 'JavaScript is great. I love javascript!', + sample: 'text.replace(/javascript/gi, "$&")', + hints: ['Use $& to reference the whole match', 'i flag for case insensitive'], + tags: ['regex', 'replace', 'highlight'], + }, + { + id: 'js-regex-074', + category: 'Regular Expressions', + difficulty: 'hard', + title: 'Match Time Format', + text: 'Find all valid 24-hour time formats in the string.', + setup: 'const text = "Meeting at 09:30 and 14:45. Invalid: 25:00 and 12:60.";', + setupCode: 'const text = "Meeting at 09:30 and 14:45. Invalid: 25:00 and 12:60.";', + expected: ['09:30', '14:45'], + sample: 'text.match(/\\b([01]\\d|2[0-3]):[0-5]\\d\\b/g)', + hints: ['Hours: 00-23, Minutes: 00-59', 'Use word boundaries'], + tags: ['regex', 'match', 'time', 'validation'], + }, + { + id: 'js-regex-075', + category: 'String Parsing', + difficulty: 'hard', + title: 'Parse Query String', + text: 'Convert query string to an object with key-value pairs.', + setup: 'const query = "name=John&age=30&city=NYC";', + setupCode: 'const query = "name=John&age=30&city=NYC";', + expected: { name: 'John', age: '30', city: 'NYC' }, + sample: 'Object.fromEntries(query.match(/[^&]+/g).map(p => p.split("=")))', + hints: ['Split by & then by =', 'Use Object.fromEntries to create object'], + tags: ['regex', 'match', 'query-string', 'parsing'], + }, + { + id: 'js-regex-076', + category: 'Regular Expressions', + difficulty: 'hard', + title: 'Unicode Flag', + text: 'Match emoji characters in the string using unicode flag.', + setup: 'const str = "Hello 👋 World 🌍!";', + setupCode: 'const str = "Hello 👋 World 🌍!";', + expected: ['👋', '🌍'], + sample: 'str.match(/\\p{Emoji}/gu)', + hints: ['Use \\p{Emoji} with u flag', 'Unicode property escapes need u flag'], + tags: ['regex', 'match', 'unicode', 'emoji'], + }, + { + id: 'js-regex-077', + category: 'String Parsing', + difficulty: 'hard', + title: 'Remove Comments', + text: 'Remove all JavaScript-style single-line comments from the code.', + setup: 'const code = "let x = 5; // initialize\\nlet y = 10; // second var";', + setupCode: 'const code = "let x = 5; // initialize\\nlet y = 10; // second var";', + expected: 'let x = 5; \nlet y = 10; ', + sample: 'code.replace(/\\/\\/.*$/gm, "")', + hints: ['Match // until end of line', 'Use m flag for multiline'], + tags: ['regex', 'replace', 'comments', 'parsing'], + }, + { + id: 'js-regex-078', + category: 'Regular Expressions', + difficulty: 'hard', + title: 'Sticky Flag', + text: 'Use sticky flag to match consecutive word characters starting at position 0.', + setup: 'const str = "Hello World";', + setupCode: 'const str = "Hello World";', + expected: 'Hello', + sample: '/\\w+/y.exec(str)[0]', + hints: ['Sticky flag matches at lastIndex position', 'Default lastIndex is 0'], + tags: ['regex', 'exec', 'sticky-flag'], + }, + { + id: 'js-regex-079', + category: 'String Parsing', + difficulty: 'hard', + title: 'Extract Numbers with Decimals', + text: 'Find all numbers including decimals and negative numbers.', + setup: 'const str = "Values: 42, -17, 3.14, -2.5, .5";', + setupCode: 'const str = "Values: 42, -17, 3.14, -2.5, .5";', + expected: ['42', '-17', '3.14', '-2.5', '.5'], + sample: 'str.match(/-?\\d*\\.?\\d+/g)', + hints: ['Optional negative, optional integer part, optional decimal', 'At least one digit required'], + tags: ['regex', 'match', 'numbers', 'parsing'], + }, + { + id: 'js-regex-080', + category: 'Regular Expressions', + difficulty: 'hard', + title: 'Validate Hex Color', + text: 'Test if string is a valid hex color (#RGB or #RRGGBB format).', + setup: 'const color = "#a1B2c3";', + setupCode: 'const color = "#a1B2c3";', + expected: true, + sample: '/^#([0-9a-fA-F]{3}){1,2}$/.test(color)', + hints: ['Match # followed by 3 or 6 hex digits', '{1,2} allows the 3-char group once or twice'], + tags: ['regex', 'test', 'color', 'validation'], + }, + { + id: 'js-regex-081', + category: 'String Parsing', + difficulty: 'easy', + title: 'Trim Whitespace', + text: 'Remove leading and trailing whitespace using regex.', + setup: 'const str = " Hello World ";', + setupCode: 'const str = " Hello World ";', + expected: 'Hello World', + sample: 'str.replace(/^\\s+|\\s+$/g, "")', + hints: ['Match whitespace at start OR end', 'Use ^ and $ anchors'], + tags: ['regex', 'replace', 'whitespace'], + }, + { + id: 'js-regex-082', + category: 'Regular Expressions', + difficulty: 'easy', + title: 'Match Entire String', + text: 'Test if the entire string consists only of letters.', + setup: 'const str = "HelloWorld";', + setupCode: 'const str = "HelloWorld";', + expected: true, + sample: '/^[a-zA-Z]+$/.test(str)', + hints: ['Use ^ and $ to match entire string', 'Only allow letters between'], + tags: ['regex', 'test', 'anchor'], + }, + { + id: 'js-regex-083', + category: 'Regular Expressions', + difficulty: 'easy', + title: 'Count Matches', + text: 'Count how many times "the" appears in the string (case insensitive).', + setup: 'const str = "The quick brown fox jumps over the lazy dog. The end.";', + setupCode: 'const str = "The quick brown fox jumps over the lazy dog. The end.";', + expected: 3, + sample: '(str.match(/the/gi) || []).length', + hints: ['Use i flag for case insensitive', 'Handle null result from match'], + tags: ['regex', 'match', 'count'], + }, + { + id: 'js-regex-084', + category: 'String Parsing', + difficulty: 'easy', + title: 'Remove Digits', + text: 'Remove all digits from the string.', + setup: 'const str = "abc123def456";', + setupCode: 'const str = "abc123def456";', + expected: 'abcdef', + sample: 'str.replace(/\\d/g, "")', + hints: ['Replace all digits with empty string', 'Use g flag for global replace'], + tags: ['regex', 'replace', 'digits'], + }, + { + id: 'js-regex-085', + category: 'Regular Expressions', + difficulty: 'easy', + title: 'Match Tab Characters', + text: 'Count the number of tab characters in the string.', + setup: 'const str = "col1\\tcol2\\tcol3\\tcol4";', + setupCode: 'const str = "col1\\tcol2\\tcol3\\tcol4";', + expected: 3, + sample: '(str.match(/\\t/g) || []).length', + hints: ['Use \\t to match tabs', 'Similar to counting any character'], + tags: ['regex', 'match', 'whitespace'], + }, + { + id: 'js-regex-086', + category: 'String Parsing', + difficulty: 'easy', + title: 'Split on Multiple Delimiters', + text: 'Split the string on comma, semicolon, or pipe.', + setup: 'const str = "a,b;c|d,e";', + setupCode: 'const str = "a,b;c|d,e";', + expected: ['a', 'b', 'c', 'd', 'e'], + sample: 'str.split(/[,;|]/)', + hints: ['Use character class for multiple delimiters', 'split accepts regex'], + tags: ['regex', 'split', 'delimiter'], + }, + { + id: 'js-regex-087', + category: 'Regular Expressions', + difficulty: 'easy', + title: 'Match Line Breaks', + text: 'Count the number of lines in the string.', + setup: 'const str = "Line 1\\nLine 2\\nLine 3\\nLine 4";', + setupCode: 'const str = "Line 1\\nLine 2\\nLine 3\\nLine 4";', + expected: 4, + sample: 'str.split(/\\n/).length', + hints: ['Split by newline', 'Number of lines = parts after split'], + tags: ['regex', 'split', 'lines'], + }, + { + id: 'js-regex-088', + category: 'String Parsing', + difficulty: 'easy', + title: 'Capitalize First Letter', + text: 'Capitalize the first letter of the string using regex.', + setup: 'const str = "hello world";', + setupCode: 'const str = "hello world";', + expected: 'Hello world', + sample: 'str.replace(/^./, c => c.toUpperCase())', + hints: ['Match first character with ^.', 'Use callback to uppercase'], + tags: ['regex', 'replace', 'capitalize'], + }, + { + id: 'js-regex-089', + category: 'Regular Expressions', + difficulty: 'easy', + title: 'Match At Least N', + text: 'Find sequences of at least 3 consecutive digits.', + setup: 'const str = "12 123 1234 12345";', + setupCode: 'const str = "12 123 1234 12345";', + expected: ['123', '1234', '12345'], + sample: 'str.match(/\\d{3,}/g)', + hints: ['Use {n,} for at least n', '{3,} means 3 or more'], + tags: ['regex', 'match', 'quantifier'], + }, + { + id: 'js-regex-090', + category: 'String Parsing', + difficulty: 'medium', + title: 'Add Thousand Separators', + text: 'Add commas as thousand separators to the number.', + setup: 'const num = "1234567890";', + setupCode: 'const num = "1234567890";', + expected: '1,234,567,890', + sample: 'num.replace(/\\B(?=(\\d{3})+(?!\\d))/g, ",")', + hints: ['Use lookahead to find positions', '\\B is non-word boundary'], + tags: ['regex', 'replace', 'formatting'], + }, + { + id: 'js-regex-091', + category: 'Regular Expressions', + difficulty: 'medium', + title: 'Match Words Starting With', + text: 'Find all words that start with "pre".', + setup: 'const str = "prevent preload unprepared prefix";', + setupCode: 'const str = "prevent preload unprepared prefix";', + expected: ['prevent', 'preload', 'prefix'], + sample: 'str.match(/\\bpre\\w*/g)', + hints: ['Use word boundary before pre', '\\w* matches rest of word'], + tags: ['regex', 'match', 'word-boundary'], + }, + { + id: 'js-regex-092', + category: 'Regular Expressions', + difficulty: 'medium', + title: 'Match Words Ending With', + text: 'Find all words that end with "ing".', + setup: 'const str = "running jumping coding thinking wing";', + setupCode: 'const str = "running jumping coding thinking wing";', + expected: ['running', 'jumping', 'coding', 'thinking'], + sample: 'str.match(/\\w+ing\\b/g)', + hints: ['\\w+ for word chars before ing', 'Word boundary after ing'], + tags: ['regex', 'match', 'word-boundary'], + }, + { + id: 'js-regex-093', + category: 'String Parsing', + difficulty: 'medium', + title: 'Extract Quoted Strings', + text: 'Extract all single-quoted strings from the text.', + setup: `const str = "Say 'hello' and 'goodbye' today";`, + setupCode: `const str = "Say 'hello' and 'goodbye' today";`, + expected: ['hello', 'goodbye'], + sample: `str.match(/'([^']+)'/g).map(s => s.slice(1, -1))`, + hints: ['Match content between quotes', 'Remove quotes from result'], + tags: ['regex', 'match', 'parsing'], + }, + { + id: 'js-regex-094', + category: 'Regular Expressions', + difficulty: 'medium', + title: 'Match Repeated Characters', + text: 'Find all sequences of 3 or more repeated characters.', + setup: 'const str = "aaabbbcccc hello woooorld";', + setupCode: 'const str = "aaabbbcccc hello woooorld";', + expected: ['aaa', 'bbb', 'cccc', 'oooo'], + sample: 'str.match(/(.)\\1{2,}/g)', + hints: ['Capture a char, then backreference', '\\1{2,} means 2+ more of same'], + tags: ['regex', 'match', 'backreference'], + }, + { + id: 'js-regex-095', + category: 'String Parsing', + difficulty: 'medium', + title: 'Normalize Whitespace', + text: 'Replace all whitespace (tabs, newlines, spaces) with single spaces.', + setup: 'const str = "Hello\\t\\tWorld\\n\\nTest";', + setupCode: 'const str = "Hello\\t\\tWorld\\n\\nTest";', + expected: 'Hello World Test', + sample: 'str.replace(/\\s+/g, " ")', + hints: ['\\s matches all whitespace types', 'Replace multiple with single'], + tags: ['regex', 'replace', 'whitespace'], + }, + { + id: 'js-regex-096', + category: 'Regular Expressions', + difficulty: 'medium', + title: 'Validate Alphanumeric', + text: 'Test if string contains only alphanumeric characters.', + setup: 'const str = "Hello123World";', + setupCode: 'const str = "Hello123World";', + expected: true, + sample: '/^[a-zA-Z0-9]+$/.test(str)', + hints: ['Match only letters and numbers', 'Use ^ and $ for entire string'], + tags: ['regex', 'test', 'validation'], + }, + { + id: 'js-regex-097', + category: 'String Parsing', + difficulty: 'medium', + title: 'Extract Parenthetical Content', + text: 'Extract all text within parentheses.', + setup: 'const str = "Call John (555-1234) or Jane (555-5678) today";', + setupCode: 'const str = "Call John (555-1234) or Jane (555-5678) today";', + expected: ['555-1234', '555-5678'], + sample: 'str.match(/\\(([^)]+)\\)/g).map(s => s.slice(1, -1))', + hints: ['Match content between parens', 'Use [^)]+ for non-paren chars'], + tags: ['regex', 'match', 'parsing'], + }, + { + id: 'js-regex-098', + category: 'Regular Expressions', + difficulty: 'hard', + title: 'Recursive Pattern Simulation', + text: 'Match nested function calls like fn(fn(x)) - extract the innermost call.', + setup: 'const str = "fn(fn(fn(x)))";', + setupCode: 'const str = "fn(fn(fn(x)))";', + expected: 'fn(x)', + sample: 'str.match(/fn\\([^()]+\\)/)[0]', + hints: ['Match fn() with no parens inside', '[^()]+ matches non-paren content'], + tags: ['regex', 'match', 'nested'], + }, + { + id: 'js-regex-099', + category: 'String Parsing', + difficulty: 'hard', + title: 'Parse Markdown Links', + text: 'Extract link text and URLs from markdown links.', + setup: 'const md = "Check [Google](https://google.com) and [GitHub](https://github.com)";', + setupCode: 'const md = "Check [Google](https://google.com) and [GitHub](https://github.com)";', + expected: [{ text: 'Google', url: 'https://google.com' }, { text: 'GitHub', url: 'https://github.com' }], + sample: '[...md.matchAll(/\\[([^\\]]+)\\]\\(([^)]+)\\)/g)].map(m => ({ text: m[1], url: m[2] }))', + hints: ['Use matchAll to get all groups', 'Capture text and URL separately'], + tags: ['regex', 'matchAll', 'markdown', 'parsing'], + }, + { + id: 'js-regex-100', + category: 'Regular Expressions', + difficulty: 'hard', + title: 'Complex Password Rules', + text: 'Validate: 8-20 chars, at least 2 uppercase, 2 lowercase, 2 digits, no consecutive repeated chars.', + setup: 'const password = "MyP4ssW0rd";', + setupCode: 'const password = "MyP4ssW0rd";', + expected: true, + sample: '/^(?=(?:.*[A-Z]){2})(?=(?:.*[a-z]){2})(?=(?:.*\\d){2})(?!.*(.)\\1).{8,20}$/.test(password)', + hints: ['Multiple lookaheads for each requirement', 'Negative lookahead for consecutive'], + tags: ['regex', 'test', 'password', 'validation'], + }, + // ======================================== + // ASYNC/AWAIT & PROMISES + // ======================================== + { + id: 'js-async-001', + category: 'Promises', + difficulty: 'easy', + title: 'Create Resolved Promise', + text: 'Create a promise that immediately resolves with the value 42.', + setup: '// Create a resolved promise', + setupCode: '// Create a resolved promise', + expected: 42, + sample: 'await Promise.resolve(42)', + hints: ['Use Promise.resolve()', 'Pass the value directly to resolve'], + tags: ['promises', 'Promise.resolve', 'basics'], + }, + { + id: 'js-async-002', + category: 'Promises', + difficulty: 'easy', + title: 'Create Rejected Promise', + text: 'Create a promise that immediately rejects with the error message "Failed".', + setup: '// Create a rejected promise and catch the error', + setupCode: '// Create a rejected promise and catch the error', + expected: 'Failed', + sample: 'await Promise.reject("Failed").catch(e => e)', + hints: ['Use Promise.reject()', 'Use .catch() to handle the rejection'], + tags: ['promises', 'Promise.reject', 'error-handling'], + }, + { + id: 'js-async-003', + category: 'Async Patterns', + difficulty: 'easy', + title: 'Basic Async Function', + text: 'Write an async function that returns the number 10.', + setup: '// Define an async function', + setupCode: '// Define an async function', + expected: 10, + sample: '(async () => 10)()', + hints: ['Async functions always return a promise', 'The return value is wrapped in Promise.resolve()'], + tags: ['async', 'await', 'basics'], + }, + { + id: 'js-async-004', + category: 'Promises', + difficulty: 'easy', + title: 'Chain Then Methods', + text: 'Chain .then() to double the resolved value.', + setup: 'const promise = Promise.resolve(5);', + setupCode: 'const promise = Promise.resolve(5);', + expected: 10, + sample: 'await promise.then(x => x * 2)', + hints: ['Use .then() to transform the value', 'Return the new value from the callback'], + tags: ['promises', 'then', 'chaining'], + }, + { + id: 'js-async-005', + category: 'Promises', + difficulty: 'easy', + title: 'Multiple Then Chains', + text: 'Chain multiple .then() calls to add 1, then multiply by 2.', + setup: 'const promise = Promise.resolve(3);', + setupCode: 'const promise = Promise.resolve(3);', + expected: 8, + sample: 'await promise.then(x => x + 1).then(x => x * 2)', + hints: ['Each .then() returns a new promise', 'Chain them together'], + tags: ['promises', 'then', 'chaining'], + }, + { + id: 'js-async-006', + category: 'Async Patterns', + difficulty: 'easy', + title: 'Await a Promise', + text: 'Use await to get the value from the promise.', + setup: 'const promise = Promise.resolve("hello");', + setupCode: 'const promise = Promise.resolve("hello");', + expected: 'hello', + sample: 'await promise', + hints: ['Use the await keyword', 'await unwraps the promise value'], + tags: ['async', 'await', 'basics'], + }, + { + id: 'js-async-007', + category: 'Promises', + difficulty: 'easy', + title: 'Promise.all with Two Promises', + text: 'Use Promise.all to wait for both promises and return their values.', + setup: 'const p1 = Promise.resolve(1); const p2 = Promise.resolve(2);', + setupCode: 'const p1 = Promise.resolve(1); const p2 = Promise.resolve(2);', + expected: [1, 2], + sample: 'await Promise.all([p1, p2])', + hints: ['Promise.all takes an array of promises', 'It returns an array of resolved values'], + tags: ['promises', 'Promise.all', 'basics'], + }, + { + id: 'js-async-008', + category: 'Promises', + difficulty: 'easy', + title: 'Promise.race Basics', + text: 'Use Promise.race to get the first resolved value.', + setup: 'const fast = Promise.resolve("fast"); const slow = new Promise(r => setTimeout(() => r("slow"), 100));', + setupCode: 'const fast = Promise.resolve("fast"); const slow = new Promise(r => setTimeout(() => r("slow"), 100));', + expected: 'fast', + sample: 'await Promise.race([fast, slow])', + hints: ['Promise.race returns the first settled promise', 'The already-resolved promise wins'], + tags: ['promises', 'Promise.race', 'basics'], + }, + { + id: 'js-async-009', + category: 'Promises', + difficulty: 'easy', + title: 'Catch Promise Error', + text: 'Catch the error from the rejected promise and return its message.', + setup: 'const promise = Promise.reject(new Error("Oops"));', + setupCode: 'const promise = Promise.reject(new Error("Oops"));', + expected: 'Oops', + sample: 'await promise.catch(e => e.message)', + hints: ['Use .catch() to handle rejections', 'Access the message property of the error'], + tags: ['promises', 'catch', 'error-handling'], + }, + { + id: 'js-async-010', + category: 'Async Patterns', + difficulty: 'easy', + title: 'Try-Catch with Await', + text: 'Use try-catch to handle the rejected promise and return the error message.', + setup: 'const promise = Promise.reject(new Error("Error occurred"));', + setupCode: 'const promise = Promise.reject(new Error("Error occurred"));', + expected: 'Error occurred', + sample: '(async () => { try { await promise; } catch (e) { return e.message; } })()', + hints: ['Wrap await in try-catch', 'Return the error message from catch block'], + tags: ['async', 'await', 'try-catch', 'error-handling'], + }, + { + id: 'js-async-011', + category: 'Promises', + difficulty: 'easy', + title: 'Finally Block', + text: 'Use .finally() to return "done" regardless of promise outcome.', + setup: 'let result = ""; const promise = Promise.resolve("success");', + setupCode: 'let result = ""; const promise = Promise.resolve("success");', + expected: 'done', + sample: 'await promise.finally(() => { result = "done"; }).then(() => result)', + hints: ['.finally() runs regardless of outcome', 'It does not receive the resolved value'], + tags: ['promises', 'finally', 'basics'], + }, + { + id: 'js-async-012', + category: 'Promises', + difficulty: 'easy', + title: 'Promise Constructor', + text: 'Create a new Promise that resolves with "created" after calling resolve.', + setup: '// Use new Promise constructor', + setupCode: '// Use new Promise constructor', + expected: 'created', + sample: 'await new Promise(resolve => resolve("created"))', + hints: ['Use new Promise((resolve, reject) => {})', 'Call resolve with the value'], + tags: ['promises', 'constructor', 'basics'], + }, + { + id: 'js-async-013', + category: 'Async Patterns', + difficulty: 'easy', + title: 'Return Value from Async', + text: 'Return the sum of two awaited promises.', + setup: 'const p1 = Promise.resolve(10); const p2 = Promise.resolve(20);', + setupCode: 'const p1 = Promise.resolve(10); const p2 = Promise.resolve(20);', + expected: 30, + sample: '(async () => await p1 + await p2)()', + hints: ['Await each promise', 'Add the results together'], + tags: ['async', 'await', 'arithmetic'], + }, + { + id: 'js-async-014', + category: 'Promises', + difficulty: 'easy', + title: 'Promise.resolve with Array', + text: 'Create a resolved promise containing an array [1, 2, 3].', + setup: '// Create a resolved promise with array', + setupCode: '// Create a resolved promise with array', + expected: [1, 2, 3], + sample: 'await Promise.resolve([1, 2, 3])', + hints: ['Promise.resolve can wrap any value', 'Arrays work just like other values'], + tags: ['promises', 'Promise.resolve', 'arrays'], + }, + { + id: 'js-async-015', + category: 'Promises', + difficulty: 'easy', + title: 'Promise.all Empty Array', + text: 'What does Promise.all return when given an empty array?', + setup: '// Call Promise.all with empty array', + setupCode: '// Call Promise.all with empty array', + expected: [], + sample: 'await Promise.all([])', + hints: ['Promise.all with empty array resolves immediately', 'It returns an empty array'], + tags: ['promises', 'Promise.all', 'edge-cases'], + }, + { + id: 'js-async-016', + category: 'Async Patterns', + difficulty: 'easy', + title: 'Async Arrow Function', + text: 'Create an async arrow function that returns "arrow".', + setup: '// Define async arrow function', + setupCode: '// Define async arrow function', + expected: 'arrow', + sample: '(async () => "arrow")()', + hints: ['Add async before the arrow function', 'Return value is automatically wrapped'], + tags: ['async', 'arrow-functions', 'basics'], + }, + { + id: 'js-async-017', + category: 'Promises', + difficulty: 'easy', + title: 'Then with Object', + text: 'Extract the name property from the resolved object.', + setup: 'const promise = Promise.resolve({ name: "Alice", age: 30 });', + setupCode: 'const promise = Promise.resolve({ name: "Alice", age: 30 });', + expected: 'Alice', + sample: 'await promise.then(obj => obj.name)', + hints: ['Use .then() to access the object', 'Return the name property'], + tags: ['promises', 'then', 'objects'], + }, + { + id: 'js-async-018', + category: 'Promises', + difficulty: 'easy', + title: 'Chained Catch', + text: 'Handle error in first promise and continue the chain.', + setup: 'const promise = Promise.reject("error").catch(() => "recovered");', + setupCode: 'const promise = Promise.reject("error").catch(() => "recovered");', + expected: 'recovered', + sample: 'await promise', + hints: ['.catch() returns a new promise', 'The chain continues with the recovered value'], + tags: ['promises', 'catch', 'chaining'], + }, + { + id: 'js-async-019', + category: 'Promises', + difficulty: 'easy', + title: 'Promise.resolve with Promise', + text: 'What happens when you call Promise.resolve on an existing promise?', + setup: 'const original = Promise.resolve(100); const wrapped = Promise.resolve(original);', + setupCode: 'const original = Promise.resolve(100); const wrapped = Promise.resolve(original);', + expected: 100, + sample: 'await wrapped', + hints: ['Promise.resolve returns the same promise if given one', 'It does not double-wrap'], + tags: ['promises', 'Promise.resolve', 'identity'], + }, + { + id: 'js-async-020', + category: 'Async Patterns', + difficulty: 'easy', + title: 'Sequential Await', + text: 'Await two promises sequentially and concatenate their string values.', + setup: 'const p1 = Promise.resolve("Hello"); const p2 = Promise.resolve("World");', + setupCode: 'const p1 = Promise.resolve("Hello"); const p2 = Promise.resolve("World");', + expected: 'HelloWorld', + sample: '(async () => (await p1) + (await p2))()', + hints: ['Await each promise in order', 'Concatenate the results'], + tags: ['async', 'await', 'strings'], + }, + { + id: 'js-async-021', + category: 'Promises', + difficulty: 'easy', + title: 'Transform Promise Value', + text: 'Use .then() to uppercase the string value.', + setup: 'const promise = Promise.resolve("hello");', + setupCode: 'const promise = Promise.resolve("hello");', + expected: 'HELLO', + sample: 'await promise.then(s => s.toUpperCase())', + hints: ['Use .then() to transform', 'Call toUpperCase() on the string'], + tags: ['promises', 'then', 'strings'], + }, + { + id: 'js-async-022', + category: 'Promises', + difficulty: 'easy', + title: 'Promise.all with Three Values', + text: 'Sum the results of three resolved promises.', + setup: 'const promises = [Promise.resolve(1), Promise.resolve(2), Promise.resolve(3)];', + setupCode: 'const promises = [Promise.resolve(1), Promise.resolve(2), Promise.resolve(3)];', + expected: 6, + sample: 'await Promise.all(promises).then(arr => arr.reduce((a, b) => a + b, 0))', + hints: ['Use Promise.all to wait for all', 'Use reduce to sum the array'], + tags: ['promises', 'Promise.all', 'reduce'], + }, + { + id: 'js-async-023', + category: 'Async Patterns', + difficulty: 'easy', + title: 'Async Function Returns Promise', + text: 'Verify that an async function returns a promise by checking its type.', + setup: 'const asyncFn = async () => 5;', + setupCode: 'const asyncFn = async () => 5;', + expected: true, + sample: 'asyncFn() instanceof Promise', + hints: ['Call the async function', 'Check if result is instanceof Promise'], + tags: ['async', 'promises', 'instanceof'], + }, + { + id: 'js-async-024', + category: 'Promises', + difficulty: 'easy', + title: 'Catch and Rethrow', + text: 'Catch an error, log it, and rethrow with a new message.', + setup: 'const promise = Promise.reject("original");', + setupCode: 'const promise = Promise.reject("original");', + expected: 'wrapped: original', + sample: 'await promise.catch(e => { throw "wrapped: " + e; }).catch(e => e)', + hints: ['First catch can throw again', 'Second catch handles the new error'], + tags: ['promises', 'catch', 'error-handling'], + }, + { + id: 'js-async-025', + category: 'Promises', + difficulty: 'easy', + title: 'Promise with Boolean', + text: 'Create a promise that resolves to true.', + setup: '// Create boolean promise', + setupCode: '// Create boolean promise', + expected: true, + sample: 'await Promise.resolve(true)', + hints: ['Promise.resolve works with booleans', 'Just pass true as the value'], + tags: ['promises', 'Promise.resolve', 'booleans'], + }, + { + id: 'js-async-026', + category: 'Promises', + difficulty: 'medium', + title: 'Promise.allSettled Basics', + text: 'Use Promise.allSettled to get the status of both resolved and rejected promises.', + setup: 'const promises = [Promise.resolve("ok"), Promise.reject("fail")];', + setupCode: 'const promises = [Promise.resolve("ok"), Promise.reject("fail")];', + expected: [{ status: 'fulfilled', value: 'ok' }, { status: 'rejected', reason: 'fail' }], + sample: 'await Promise.allSettled(promises)', + hints: ['Promise.allSettled never rejects', 'It returns objects with status and value/reason'], + tags: ['promises', 'Promise.allSettled', 'error-handling'], + }, + { + id: 'js-async-027', + category: 'Promises', + difficulty: 'medium', + title: 'Promise.any First Success', + text: 'Use Promise.any to get the first successful result.', + setup: 'const promises = [Promise.reject("err1"), Promise.resolve("success"), Promise.reject("err2")];', + setupCode: 'const promises = [Promise.reject("err1"), Promise.resolve("success"), Promise.reject("err2")];', + expected: 'success', + sample: 'await Promise.any(promises)', + hints: ['Promise.any returns first fulfilled promise', 'It ignores rejections until all fail'], + tags: ['promises', 'Promise.any', 'basics'], + }, + { + id: 'js-async-028', + category: 'Event Loop', + difficulty: 'medium', + title: 'Microtask vs Macrotask Order', + text: 'Predict the order: Promise.then runs before setTimeout.', + setup: 'const order = []; setTimeout(() => order.push("timeout"), 0); Promise.resolve().then(() => order.push("promise"));', + setupCode: 'const order = []; setTimeout(() => order.push("timeout"), 0); Promise.resolve().then(() => order.push("promise"));', + expected: ['promise', 'timeout'], + sample: 'await new Promise(r => setTimeout(r, 10)).then(() => order)', + hints: ['Microtasks run before macrotasks', 'Promise.then is a microtask, setTimeout is macrotask'], + tags: ['event-loop', 'microtasks', 'macrotasks'], + }, + { + id: 'js-async-029', + category: 'Async Patterns', + difficulty: 'medium', + title: 'Parallel Promise Execution', + text: 'Run promises in parallel and return when all complete.', + setup: 'const delay = (ms, val) => new Promise(r => setTimeout(() => r(val), ms));', + setupCode: 'const delay = (ms, val) => new Promise(r => setTimeout(() => r(val), ms));', + expected: ['a', 'b', 'c'], + sample: 'await Promise.all([delay(10, "a"), delay(20, "b"), delay(15, "c")])', + hints: ['Use Promise.all for parallel execution', 'Results are in the same order as input'], + tags: ['async', 'Promise.all', 'parallel'], + }, + { + id: 'js-async-030', + category: 'Promises', + difficulty: 'medium', + title: 'Promise Chain with Map', + text: 'Map over an array to create promises, then wait for all.', + setup: 'const nums = [1, 2, 3];', + setupCode: 'const nums = [1, 2, 3];', + expected: [2, 4, 6], + sample: 'await Promise.all(nums.map(n => Promise.resolve(n * 2)))', + hints: ['Map creates an array of promises', 'Promise.all waits for all'], + tags: ['promises', 'Promise.all', 'map'], + }, + { + id: 'js-async-031', + category: 'Async Patterns', + difficulty: 'medium', + title: 'Sequential Promise Execution', + text: 'Execute promises one after another, collecting results.', + setup: 'const tasks = [() => Promise.resolve(1), () => Promise.resolve(2), () => Promise.resolve(3)];', + setupCode: 'const tasks = [() => Promise.resolve(1), () => Promise.resolve(2), () => Promise.resolve(3)];', + expected: [1, 2, 3], + sample: 'await tasks.reduce(async (acc, fn) => [...await acc, await fn()], Promise.resolve([]))', + hints: ['Use reduce for sequential execution', 'Await the accumulator before adding new result'], + tags: ['async', 'await', 'sequential', 'reduce'], + }, + { + id: 'js-async-032', + category: 'Promises', + difficulty: 'medium', + title: 'Promise.race with Timeout', + text: 'Implement a timeout using Promise.race.', + setup: 'const slow = new Promise(r => setTimeout(() => r("slow"), 100)); const timeout = new Promise((_, rej) => setTimeout(() => rej("timeout"), 50));', + setupCode: 'const slow = new Promise(r => setTimeout(() => r("slow"), 100)); const timeout = new Promise((_, rej) => setTimeout(() => rej("timeout"), 50));', + expected: 'timeout', + sample: 'await Promise.race([slow, timeout]).catch(e => e)', + hints: ['Promise.race returns first settled', 'The timeout rejection wins'], + tags: ['promises', 'Promise.race', 'timeout'], + }, + { + id: 'js-async-033', + category: 'Async Patterns', + difficulty: 'medium', + title: 'Async Error Propagation', + text: 'Throw an error in an async function and catch it.', + setup: 'const asyncError = async () => { throw new Error("async error"); };', + setupCode: 'const asyncError = async () => { throw new Error("async error"); };', + expected: 'async error', + sample: 'await asyncError().catch(e => e.message)', + hints: ['Thrown errors become rejections', 'Catch with .catch() or try-catch'], + tags: ['async', 'error-handling', 'throw'], + }, + { + id: 'js-async-034', + category: 'Promises', + difficulty: 'medium', + title: 'Promisify Callback Function', + text: 'Convert a callback-style function to return a promise.', + setup: 'const callbackFn = (val, cb) => setTimeout(() => cb(null, val * 2), 10);', + setupCode: 'const callbackFn = (val, cb) => setTimeout(() => cb(null, val * 2), 10);', + expected: 10, + sample: 'await new Promise((resolve, reject) => callbackFn(5, (err, result) => err ? reject(err) : resolve(result)))', + hints: ['Wrap the callback function in new Promise', 'Call resolve or reject based on callback args'], + tags: ['promises', 'promisify', 'callbacks'], + }, + { + id: 'js-async-035', + category: 'Promises', + difficulty: 'medium', + title: 'Promise.all Rejection', + text: 'What happens when one promise in Promise.all rejects?', + setup: 'const promises = [Promise.resolve(1), Promise.reject("error"), Promise.resolve(3)];', + setupCode: 'const promises = [Promise.resolve(1), Promise.reject("error"), Promise.resolve(3)];', + expected: 'error', + sample: 'await Promise.all(promises).catch(e => e)', + hints: ['Promise.all fails fast', 'One rejection rejects the whole thing'], + tags: ['promises', 'Promise.all', 'error-handling'], + }, + { + id: 'js-async-036', + category: 'Async Patterns', + difficulty: 'medium', + title: 'Async IIFE', + text: 'Use an async IIFE to immediately execute async code.', + setup: 'let value = 0;', + setupCode: 'let value = 0;', + expected: 42, + sample: '(async () => { value = await Promise.resolve(42); return value; })()', + hints: ['Wrap async function in parentheses', 'Immediately invoke it with ()'], + tags: ['async', 'IIFE', 'patterns'], + }, + { + id: 'js-async-037', + category: 'Promises', + difficulty: 'medium', + title: 'Chained Promise Transformation', + text: 'Chain multiple transformations: add 5, multiply by 2, subtract 3.', + setup: 'const promise = Promise.resolve(10);', + setupCode: 'const promise = Promise.resolve(10);', + expected: 27, + sample: 'await promise.then(x => x + 5).then(x => x * 2).then(x => x - 3)', + hints: ['Each .then() transforms the value', 'Chain them in order of operations'], + tags: ['promises', 'then', 'chaining'], + }, + { + id: 'js-async-038', + category: 'Event Loop', + difficulty: 'medium', + title: 'queueMicrotask Usage', + text: 'Use queueMicrotask to schedule a microtask.', + setup: 'const order = [];', + setupCode: 'const order = [];', + expected: ['sync', 'microtask', 'timeout'], + sample: '(() => { order.push("sync"); queueMicrotask(() => order.push("microtask")); setTimeout(() => order.push("timeout"), 0); return new Promise(r => setTimeout(r, 20)); })().then(() => order)', + hints: ['queueMicrotask schedules a microtask', 'Microtasks run before setTimeout'], + tags: ['event-loop', 'microtasks', 'queueMicrotask'], + }, + { + id: 'js-async-039', + category: 'Async Patterns', + difficulty: 'medium', + title: 'Conditional Await', + text: 'Await only if the value is a promise.', + setup: 'const maybePromise = Promise.resolve(100); const notPromise = 50;', + setupCode: 'const maybePromise = Promise.resolve(100); const notPromise = 50;', + expected: 150, + sample: '(async () => (await maybePromise) + notPromise)()', + hints: ['Await works on non-promises too', 'It just returns the value immediately'], + tags: ['async', 'await', 'conditional'], + }, + { + id: 'js-async-040', + category: 'Promises', + difficulty: 'medium', + title: 'Promise.allSettled Filter Fulfilled', + text: 'Filter only the fulfilled results from Promise.allSettled.', + setup: 'const promises = [Promise.resolve(1), Promise.reject("err"), Promise.resolve(3)];', + setupCode: 'const promises = [Promise.resolve(1), Promise.reject("err"), Promise.resolve(3)];', + expected: [1, 3], + sample: 'await Promise.allSettled(promises).then(results => results.filter(r => r.status === "fulfilled").map(r => r.value))', + hints: ['Filter by status property', 'Map to extract values'], + tags: ['promises', 'Promise.allSettled', 'filter'], + }, + { + id: 'js-async-041', + category: 'Async Patterns', + difficulty: 'medium', + title: 'Async Array Filter', + text: 'Filter an array asynchronously based on async predicate.', + setup: 'const nums = [1, 2, 3, 4, 5]; const asyncIsEven = async n => n % 2 === 0;', + setupCode: 'const nums = [1, 2, 3, 4, 5]; const asyncIsEven = async n => n % 2 === 0;', + expected: [2, 4], + sample: 'await Promise.all(nums.map(async n => ({ n, keep: await asyncIsEven(n) }))).then(results => results.filter(r => r.keep).map(r => r.n))', + hints: ['Map to check each item', 'Filter based on async result'], + tags: ['async', 'filter', 'arrays'], + }, + { + id: 'js-async-042', + category: 'Promises', + difficulty: 'medium', + title: 'Promise with Delay', + text: 'Create a delay function that resolves after specified milliseconds.', + setup: 'const start = Date.now();', + setupCode: 'const start = Date.now();', + expected: true, + sample: 'await new Promise(r => setTimeout(r, 50)).then(() => Date.now() - start >= 50)', + hints: ['Use setTimeout inside Promise', 'Resolve after the timeout'], + tags: ['promises', 'setTimeout', 'delay'], + }, + { + id: 'js-async-043', + category: 'Async Patterns', + difficulty: 'medium', + title: 'Async Reduce', + text: 'Use reduce with async/await to sum values sequentially.', + setup: 'const asyncNums = [Promise.resolve(1), Promise.resolve(2), Promise.resolve(3)];', + setupCode: 'const asyncNums = [Promise.resolve(1), Promise.resolve(2), Promise.resolve(3)];', + expected: 6, + sample: 'await asyncNums.reduce(async (acc, p) => (await acc) + (await p), Promise.resolve(0))', + hints: ['Await the accumulator first', 'Then await the current promise'], + tags: ['async', 'reduce', 'sequential'], + }, + { + id: 'js-async-044', + category: 'Promises', + difficulty: 'medium', + title: 'Promise.any All Rejected', + text: 'Handle when all promises passed to Promise.any reject.', + setup: 'const promises = [Promise.reject("a"), Promise.reject("b"), Promise.reject("c")];', + setupCode: 'const promises = [Promise.reject("a"), Promise.reject("b"), Promise.reject("c")];', + expected: ['a', 'b', 'c'], + sample: 'await Promise.any(promises).catch(e => e.errors)', + hints: ['Promise.any throws AggregateError', 'Access errors property for all rejection reasons'], + tags: ['promises', 'Promise.any', 'AggregateError'], + }, + { + id: 'js-async-045', + category: 'Event Loop', + difficulty: 'medium', + title: 'Multiple Microtasks Order', + text: 'Predict the order of multiple chained promises.', + setup: 'const order = []; Promise.resolve().then(() => order.push(1)).then(() => order.push(2)); Promise.resolve().then(() => order.push(3));', + setupCode: 'const order = []; Promise.resolve().then(() => order.push(1)).then(() => order.push(2)); Promise.resolve().then(() => order.push(3));', + expected: [1, 3, 2], + sample: 'await new Promise(r => setTimeout(r, 10)).then(() => order)', + hints: ['First .then() from each chain runs first', 'Chained .then() is scheduled after'], + tags: ['event-loop', 'microtasks', 'order'], + }, + { + id: 'js-async-046', + category: 'Async Patterns', + difficulty: 'medium', + title: 'Async Method in Object', + text: 'Define an object with an async method.', + setup: 'const obj = { async getValue() { return 42; } };', + setupCode: 'const obj = { async getValue() { return 42; } };', + expected: 42, + sample: 'await obj.getValue()', + hints: ['Async methods work like async functions', 'Await the method call'], + tags: ['async', 'objects', 'methods'], + }, + { + id: 'js-async-047', + category: 'Promises', + difficulty: 'medium', + title: 'Promise Chain Recovery', + text: 'Recover from an error in the middle of a promise chain.', + setup: 'const chain = Promise.resolve(10).then(x => { throw "error"; }).catch(() => 0).then(x => x + 5);', + setupCode: 'const chain = Promise.resolve(10).then(x => { throw "error"; }).catch(() => 0).then(x => x + 5);', + expected: 5, + sample: 'await chain', + hints: ['catch() recovers the chain', 'The next .then() receives the recovered value'], + tags: ['promises', 'catch', 'recovery'], + }, + { + id: 'js-async-048', + category: 'Async Patterns', + difficulty: 'medium', + title: 'Async Class Constructor Pattern', + text: 'Use a static async factory method for async initialization.', + setup: 'class AsyncClass { constructor(data) { this.data = data; } static async create() { const data = await Promise.resolve("loaded"); return new AsyncClass(data); } }', + setupCode: 'class AsyncClass { constructor(data) { this.data = data; } static async create() { const data = await Promise.resolve("loaded"); return new AsyncClass(data); } }', + expected: 'loaded', + sample: 'await AsyncClass.create().then(instance => instance.data)', + hints: ['Constructors cannot be async', 'Use static factory method instead'], + tags: ['async', 'classes', 'factory-pattern'], + }, + { + id: 'js-async-049', + category: 'Promises', + difficulty: 'medium', + title: 'Promise.resolve Thenable', + text: 'Promise.resolve unwraps thenable objects.', + setup: 'const thenable = { then(resolve) { resolve("from thenable"); } };', + setupCode: 'const thenable = { then(resolve) { resolve("from thenable"); } };', + expected: 'from thenable', + sample: 'await Promise.resolve(thenable)', + hints: ['A thenable has a then method', 'Promise.resolve calls the then method'], + tags: ['promises', 'thenable', 'Promise.resolve'], + }, + { + id: 'js-async-050', + category: 'Async Patterns', + difficulty: 'medium', + title: 'Parallel with Limit', + text: 'Execute promises with a concurrency limit of 2.', + setup: 'const tasks = [() => Promise.resolve(1), () => Promise.resolve(2), () => Promise.resolve(3), () => Promise.resolve(4)];', + setupCode: 'const tasks = [() => Promise.resolve(1), () => Promise.resolve(2), () => Promise.resolve(3), () => Promise.resolve(4)];', + expected: [1, 2, 3, 4], + sample: '(async () => { const results = []; for (let i = 0; i < tasks.length; i += 2) { results.push(...await Promise.all(tasks.slice(i, i + 2).map(t => t()))); } return results; })()', + hints: ['Process in batches', 'Use slice to get batches of tasks'], + tags: ['async', 'concurrency', 'batching'], + }, + { + id: 'js-async-051', + category: 'Promises', + difficulty: 'medium', + title: 'Then Returns Promise', + text: 'Return a promise from .then() to chain asynchronously.', + setup: 'const promise = Promise.resolve(5);', + setupCode: 'const promise = Promise.resolve(5);', + expected: 10, + sample: 'await promise.then(x => Promise.resolve(x * 2))', + hints: ['Returning a promise from then chains it', 'The next then receives the resolved value'], + tags: ['promises', 'then', 'chaining'], + }, + { + id: 'js-async-052', + category: 'Event Loop', + difficulty: 'medium', + title: 'setImmediate vs setTimeout', + text: 'Compare execution order of different scheduling methods.', + setup: 'const order = [];', + setupCode: 'const order = [];', + expected: ['promise', 'timeout'], + sample: '(() => { setTimeout(() => order.push("timeout"), 0); Promise.resolve().then(() => order.push("promise")); return new Promise(r => setTimeout(r, 20)); })().then(() => order)', + hints: ['Promise.then is a microtask', 'setTimeout is a macrotask'], + tags: ['event-loop', 'setTimeout', 'microtasks'], + }, + { + id: 'js-async-053', + category: 'Async Patterns', + difficulty: 'medium', + title: 'Async Generator Basics', + text: 'Create an async generator that yields 1, 2, 3.', + setup: 'async function* gen() { yield 1; yield 2; yield 3; }', + setupCode: 'async function* gen() { yield 1; yield 2; yield 3; }', + expected: [1, 2, 3], + sample: '(async () => { const result = []; for await (const x of gen()) result.push(x); return result; })()', + hints: ['Use for await...of to iterate', 'Collect values in an array'], + tags: ['async', 'generators', 'for-await'], + }, + { + id: 'js-async-054', + category: 'Promises', + difficulty: 'medium', + title: 'Promise.race with All Resolved', + text: 'Get the fastest promise when all resolve.', + setup: 'const p1 = new Promise(r => setTimeout(() => r("first"), 10)); const p2 = new Promise(r => setTimeout(() => r("second"), 50));', + setupCode: 'const p1 = new Promise(r => setTimeout(() => r("first"), 10)); const p2 = new Promise(r => setTimeout(() => r("second"), 50));', + expected: 'first', + sample: 'await Promise.race([p1, p2])', + hints: ['Promise.race returns first to settle', 'The faster promise wins'], + tags: ['promises', 'Promise.race', 'timing'], + }, + { + id: 'js-async-055', + category: 'Async Patterns', + difficulty: 'medium', + title: 'Async Map Function', + text: 'Implement an async map that processes items in parallel.', + setup: 'const items = [1, 2, 3]; const asyncDouble = async x => x * 2;', + setupCode: 'const items = [1, 2, 3]; const asyncDouble = async x => x * 2;', + expected: [2, 4, 6], + sample: 'await Promise.all(items.map(asyncDouble))', + hints: ['Map creates array of promises', 'Promise.all waits for all'], + tags: ['async', 'map', 'Promise.all'], + }, + { + id: 'js-async-056', + category: 'Promises', + difficulty: 'medium', + title: 'Deferred Promise Pattern', + text: 'Create a deferred promise that can be resolved externally.', + setup: 'let resolver; const deferred = new Promise(r => { resolver = r; });', + setupCode: 'let resolver; const deferred = new Promise(r => { resolver = r; });', + expected: 'resolved externally', + sample: '(() => { resolver("resolved externally"); return deferred; })()', + hints: ['Store resolve function in outer variable', 'Call it later to resolve'], + tags: ['promises', 'deferred', 'patterns'], + }, + { + id: 'js-async-057', + category: 'Async Patterns', + difficulty: 'medium', + title: 'Async ForEach', + text: 'Process items sequentially with async forEach.', + setup: 'const items = [1, 2, 3]; const results = [];', + setupCode: 'const items = [1, 2, 3]; const results = [];', + expected: [1, 2, 3], + sample: '(async () => { for (const item of items) { results.push(await Promise.resolve(item)); } return results; })()', + hints: ['Regular forEach does not wait', 'Use for...of loop instead'], + tags: ['async', 'forEach', 'sequential'], + }, + { + id: 'js-async-058', + category: 'Promises', + difficulty: 'medium', + title: 'Multiple Catch Handlers', + text: 'Chain multiple catch handlers.', + setup: 'const promise = Promise.reject(new Error("original"));', + setupCode: 'const promise = Promise.reject(new Error("original"));', + expected: 'handled', + sample: 'await promise.catch(e => { throw new Error("rethrown"); }).catch(() => "handled")', + hints: ['First catch can rethrow', 'Second catch handles the new error'], + tags: ['promises', 'catch', 'chaining'], + }, + { + id: 'js-async-059', + category: 'Event Loop', + difficulty: 'medium', + title: 'Nested setTimeout', + text: 'Predict order with nested setTimeout calls.', + setup: 'const order = [];', + setupCode: 'const order = [];', + expected: ['outer', 'inner'], + sample: '(() => { setTimeout(() => { order.push("outer"); setTimeout(() => order.push("inner"), 0); }, 0); return new Promise(r => setTimeout(r, 50)); })().then(() => order)', + hints: ['Outer timeout runs first', 'Inner timeout is scheduled when outer runs'], + tags: ['event-loop', 'setTimeout', 'nesting'], + }, + { + id: 'js-async-060', + category: 'Async Patterns', + difficulty: 'medium', + title: 'Promise-based Retry', + text: 'Retry a failing async operation up to 3 times.', + setup: 'let attempts = 0; const flaky = async () => { attempts++; if (attempts < 3) throw "fail"; return "success"; };', + setupCode: 'let attempts = 0; const flaky = async () => { attempts++; if (attempts < 3) throw "fail"; return "success"; };', + expected: 'success', + sample: '(async () => { for (let i = 0; i < 3; i++) { try { return await flaky(); } catch (e) { if (i === 2) throw e; } } })()', + hints: ['Loop with try-catch', 'Return on success, continue on failure'], + tags: ['async', 'retry', 'error-handling'], + }, + { + id: 'js-async-061', + category: 'Promises', + difficulty: 'hard', + title: 'Promise.all Short Circuit', + text: 'Demonstrate that Promise.all rejects immediately on first rejection.', + setup: 'const p1 = new Promise((_, rej) => setTimeout(() => rej("fast reject"), 10)); const p2 = new Promise(r => setTimeout(() => r("slow"), 100)); let p2Resolved = false; p2.then(() => p2Resolved = true);', + setupCode: 'const p1 = new Promise((_, rej) => setTimeout(() => rej("fast reject"), 10)); const p2 = new Promise(r => setTimeout(() => r("slow"), 100)); let p2Resolved = false; p2.then(() => p2Resolved = true);', + expected: 'fast reject', + sample: 'await Promise.all([p1, p2]).catch(e => e)', + hints: ['Promise.all fails fast', 'Other promises keep running but result is ignored'], + tags: ['promises', 'Promise.all', 'rejection'], + }, + { + id: 'js-async-062', + category: 'Async Patterns', + difficulty: 'hard', + title: 'Async Iterator Protocol', + text: 'Implement the async iterator protocol manually.', + setup: 'const asyncIterable = { [Symbol.asyncIterator]() { let i = 0; return { async next() { if (i < 3) return { value: ++i, done: false }; return { done: true }; } }; } };', + setupCode: 'const asyncIterable = { [Symbol.asyncIterator]() { let i = 0; return { async next() { if (i < 3) return { value: ++i, done: false }; return { done: true }; } }; } };', + expected: [1, 2, 3], + sample: '(async () => { const result = []; for await (const x of asyncIterable) result.push(x); return result; })()', + hints: ['Implement Symbol.asyncIterator', 'Return object with async next()'], + tags: ['async', 'iterators', 'Symbol.asyncIterator'], + }, + { + id: 'js-async-063', + category: 'Promises', + difficulty: 'hard', + title: 'Promise Executor Error', + text: 'Handle an error thrown synchronously in the Promise executor.', + setup: 'const badPromise = new Promise(() => { throw new Error("executor error"); });', + setupCode: 'const badPromise = new Promise(() => { throw new Error("executor error"); });', + expected: 'executor error', + sample: 'await badPromise.catch(e => e.message)', + hints: ['Errors in executor become rejections', 'Catch them normally'], + tags: ['promises', 'executor', 'error-handling'], + }, + { + id: 'js-async-064', + category: 'Event Loop', + difficulty: 'hard', + title: 'Complex Event Loop Order', + text: 'Predict the execution order with mixed micro and macro tasks.', + setup: 'const order = [];', + setupCode: 'const order = [];', + expected: ['sync', 'micro1', 'micro2', 'timeout'], + sample: '(() => { order.push("sync"); Promise.resolve().then(() => { order.push("micro1"); Promise.resolve().then(() => order.push("micro2")); }); setTimeout(() => order.push("timeout"), 0); return new Promise(r => setTimeout(r, 20)); })().then(() => order)', + hints: ['Sync runs first', 'Microtasks exhaust before macrotasks'], + tags: ['event-loop', 'microtasks', 'macrotasks', 'order'], + }, + { + id: 'js-async-065', + category: 'Async Patterns', + difficulty: 'hard', + title: 'Cancellable Promise', + text: 'Implement a cancellable promise using AbortController.', + setup: 'const controller = new AbortController(); const cancellable = (signal) => new Promise((resolve, reject) => { const id = setTimeout(() => resolve("done"), 100); signal.addEventListener("abort", () => { clearTimeout(id); reject("cancelled"); }); });', + setupCode: 'const controller = new AbortController(); const cancellable = (signal) => new Promise((resolve, reject) => { const id = setTimeout(() => resolve("done"), 100); signal.addEventListener("abort", () => { clearTimeout(id); reject("cancelled"); }); });', + expected: 'cancelled', + sample: '(() => { const p = cancellable(controller.signal); setTimeout(() => controller.abort(), 10); return p.catch(e => e); })()', + hints: ['Use AbortController signal', 'Listen for abort event'], + tags: ['async', 'AbortController', 'cancellation'], + }, + { + id: 'js-async-066', + category: 'Promises', + difficulty: 'hard', + title: 'Promise.allSettled Analysis', + text: 'Analyze results to count fulfilled and rejected promises.', + setup: 'const promises = [Promise.resolve(1), Promise.reject("a"), Promise.resolve(2), Promise.reject("b"), Promise.resolve(3)];', + setupCode: 'const promises = [Promise.resolve(1), Promise.reject("a"), Promise.resolve(2), Promise.reject("b"), Promise.resolve(3)];', + expected: { fulfilled: 3, rejected: 2 }, + sample: 'await Promise.allSettled(promises).then(results => results.reduce((acc, r) => { acc[r.status]++; return acc; }, { fulfilled: 0, rejected: 0 }))', + hints: ['Use reduce to count statuses', 'Initialize counters for both statuses'], + tags: ['promises', 'Promise.allSettled', 'reduce'], + }, + { + id: 'js-async-067', + category: 'Async Patterns', + difficulty: 'hard', + title: 'Async Pool Implementation', + text: 'Implement a promise pool that runs at most N promises concurrently.', + setup: 'const delay = (ms, val) => new Promise(r => setTimeout(() => r(val), ms)); const tasks = [() => delay(30, 1), () => delay(20, 2), () => delay(10, 3), () => delay(40, 4)];', + setupCode: 'const delay = (ms, val) => new Promise(r => setTimeout(() => r(val), ms)); const tasks = [() => delay(30, 1), () => delay(20, 2), () => delay(10, 3), () => delay(40, 4)];', + expected: [1, 2, 3, 4], + sample: '(async () => { const results = []; const executing = []; for (const task of tasks) { const p = task().then(r => { executing.splice(executing.indexOf(p), 1); return r; }); results.push(p); executing.push(p); if (executing.length >= 2) await Promise.race(executing); } return Promise.all(results); })()', + hints: ['Track executing promises', 'Wait when pool is full'], + tags: ['async', 'concurrency', 'pool'], + }, + { + id: 'js-async-068', + category: 'Promises', + difficulty: 'hard', + title: 'Promise.race Rejection Wins', + text: 'Handle when rejection wins the race.', + setup: 'const reject = new Promise((_, r) => setTimeout(() => r("error"), 10)); const resolve = new Promise(r => setTimeout(() => r("success"), 50));', + setupCode: 'const reject = new Promise((_, r) => setTimeout(() => r("error"), 10)); const resolve = new Promise(r => setTimeout(() => r("success"), 50));', + expected: 'error', + sample: 'await Promise.race([reject, resolve]).catch(e => e)', + hints: ['Rejection can win the race', 'Use catch to handle it'], + tags: ['promises', 'Promise.race', 'rejection'], + }, + { + id: 'js-async-069', + category: 'Event Loop', + difficulty: 'hard', + title: 'Promise Resolution in setTimeout', + text: 'Understand when promises resolve inside setTimeout.', + setup: 'const order = [];', + setupCode: 'const order = [];', + expected: ['timeout1', 'micro', 'timeout2'], + sample: '(() => { setTimeout(() => { order.push("timeout1"); Promise.resolve().then(() => order.push("micro")); }, 0); setTimeout(() => order.push("timeout2"), 0); return new Promise(r => setTimeout(r, 30)); })().then(() => order)', + hints: ['Each setTimeout callback gets its own microtask queue', 'Microtasks run before next macrotask'], + tags: ['event-loop', 'setTimeout', 'microtasks'], + }, + { + id: 'js-async-070', + category: 'Async Patterns', + difficulty: 'hard', + title: 'Async Mutex', + text: 'Implement a simple mutex using promises.', + setup: 'class Mutex { constructor() { this.locked = false; this.queue = []; } async acquire() { if (this.locked) { await new Promise(r => this.queue.push(r)); } this.locked = true; } release() { this.locked = false; if (this.queue.length > 0) this.queue.shift()(); } }', + setupCode: 'class Mutex { constructor() { this.locked = false; this.queue = []; } async acquire() { if (this.locked) { await new Promise(r => this.queue.push(r)); } this.locked = true; } release() { this.locked = false; if (this.queue.length > 0) this.queue.shift()(); } }', + expected: [1, 2, 3], + sample: '(async () => { const mutex = new Mutex(); const results = []; const task = async (n) => { await mutex.acquire(); results.push(n); mutex.release(); }; await Promise.all([task(1), task(2), task(3)]); return results; })()', + hints: ['Queue waiters when locked', 'Release wakes up next waiter'], + tags: ['async', 'mutex', 'concurrency'], + }, + { + id: 'js-async-071', + category: 'Promises', + difficulty: 'hard', + title: 'Promise Chain State', + text: 'Understand that each .then() creates a new promise.', + setup: 'const p1 = Promise.resolve(1); const p2 = p1.then(x => x + 1); const p3 = p1.then(x => x + 2);', + setupCode: 'const p1 = Promise.resolve(1); const p2 = p1.then(x => x + 1); const p3 = p1.then(x => x + 2);', + expected: [2, 3], + sample: 'await Promise.all([p2, p3])', + hints: ['Each then creates independent chain', 'Both branch from same promise'], + tags: ['promises', 'then', 'branching'], + }, + { + id: 'js-async-072', + category: 'Async Patterns', + difficulty: 'hard', + title: 'Async Event Emitter', + text: 'Wait for an event using a promise.', + setup: 'class Emitter { constructor() { this.listeners = {}; } on(event, cb) { (this.listeners[event] ||= []).push(cb); } emit(event, data) { (this.listeners[event] || []).forEach(cb => cb(data)); } } const emitter = new Emitter();', + setupCode: 'class Emitter { constructor() { this.listeners = {}; } on(event, cb) { (this.listeners[event] ||= []).push(cb); } emit(event, data) { (this.listeners[event] || []).forEach(cb => cb(data)); } } const emitter = new Emitter();', + expected: 'event data', + sample: '(() => { const promise = new Promise(r => emitter.on("data", r)); setTimeout(() => emitter.emit("data", "event data"), 10); return promise; })()', + hints: ['Create promise that resolves on event', 'Store resolve as event listener'], + tags: ['async', 'events', 'patterns'], + }, + { + id: 'js-async-073', + category: 'Promises', + difficulty: 'hard', + title: 'Dynamic Promise Chain', + text: 'Build a promise chain dynamically from an array of functions.', + setup: 'const operations = [x => x + 1, x => x * 2, x => x - 3];', + setupCode: 'const operations = [x => x + 1, x => x * 2, x => x - 3];', + expected: 9, + sample: 'await operations.reduce((p, fn) => p.then(fn), Promise.resolve(5))', + hints: ['Use reduce to build chain', 'Start with resolved promise'], + tags: ['promises', 'reduce', 'dynamic-chain'], + }, + { + id: 'js-async-074', + category: 'Event Loop', + difficulty: 'hard', + title: 'Nested Promise Resolution', + text: 'Understand nested promise resolution timing.', + setup: 'const order = [];', + setupCode: 'const order = [];', + expected: ['p1', 'p2', 'p1-inner', 'p2-inner'], + sample: '(() => { Promise.resolve().then(() => { order.push("p1"); Promise.resolve().then(() => order.push("p1-inner")); }); Promise.resolve().then(() => { order.push("p2"); Promise.resolve().then(() => order.push("p2-inner")); }); return new Promise(r => setTimeout(r, 20)); })().then(() => order)', + hints: ['Outer thens run first', 'Inner thens are queued after'], + tags: ['event-loop', 'microtasks', 'nesting'], + }, + { + id: 'js-async-075', + category: 'Async Patterns', + difficulty: 'hard', + title: 'Async Generator with Delay', + text: 'Create an async generator that yields values with delays.', + setup: 'async function* delayedGen() { for (let i = 1; i <= 3; i++) { await new Promise(r => setTimeout(r, 10)); yield i; } }', + setupCode: 'async function* delayedGen() { for (let i = 1; i <= 3; i++) { await new Promise(r => setTimeout(r, 10)); yield i; } }', + expected: [1, 2, 3], + sample: '(async () => { const result = []; for await (const x of delayedGen()) result.push(x); return result; })()', + hints: ['Await delay before each yield', 'Use for await to consume'], + tags: ['async', 'generators', 'delay'], + }, + { + id: 'js-async-076', + category: 'Promises', + difficulty: 'hard', + title: 'Promise.any vs Promise.race', + text: 'Compare behavior when first promise rejects.', + setup: 'const p1 = Promise.reject("err"); const p2 = new Promise(r => setTimeout(() => r("ok"), 20));', + setupCode: 'const p1 = Promise.reject("err"); const p2 = new Promise(r => setTimeout(() => r("ok"), 20));', + expected: { race: 'err', any: 'ok' }, + sample: 'await Promise.all([Promise.race([p1, p2]).catch(e => e), Promise.any([p1, p2])]).then(([race, any]) => ({ race, any }))', + hints: ['race returns first settled (even rejection)', 'any waits for first fulfilled'], + tags: ['promises', 'Promise.any', 'Promise.race'], + }, + { + id: 'js-async-077', + category: 'Async Patterns', + difficulty: 'hard', + title: 'Async Transform Stream', + text: 'Process data through multiple async transformations.', + setup: 'const transforms = [async x => x * 2, async x => x + 10, async x => x.toString()];', + setupCode: 'const transforms = [async x => x * 2, async x => x + 10, async x => x.toString()];', + expected: '30', + sample: 'await transforms.reduce(async (acc, fn) => fn(await acc), Promise.resolve(10))', + hints: ['Reduce with async accumulator', 'Await before passing to next'], + tags: ['async', 'transform', 'reduce'], + }, + { + id: 'js-async-078', + category: 'Promises', + difficulty: 'hard', + title: 'Promise Memory Leak Prevention', + text: 'Properly clean up when a promise is no longer needed.', + setup: 'let cleanup = false; const createPromise = () => { const id = setTimeout(() => {}, 1000); return { promise: new Promise(r => setTimeout(r, 100)), cancel: () => { clearTimeout(id); cleanup = true; } }; };', + setupCode: 'let cleanup = false; const createPromise = () => { const id = setTimeout(() => {}, 1000); return { promise: new Promise(r => setTimeout(r, 100)), cancel: () => { clearTimeout(id); cleanup = true; } }; };', + expected: true, + sample: '(async () => { const { promise, cancel } = createPromise(); cancel(); return cleanup; })()', + hints: ['Return cleanup function with promise', 'Call it when no longer needed'], + tags: ['promises', 'cleanup', 'memory'], + }, + { + id: 'js-async-079', + category: 'Event Loop', + difficulty: 'hard', + title: 'Promise in Promise Constructor', + text: 'Understand behavior of awaiting inside Promise constructor.', + setup: 'const order = [];', + setupCode: 'const order = [];', + expected: ['constructor sync', 'outer sync', 'inner resolved'], + sample: '(() => { new Promise(async (resolve) => { order.push("constructor sync"); await Promise.resolve(); order.push("inner resolved"); resolve(); }); order.push("outer sync"); return new Promise(r => setTimeout(r, 20)); })().then(() => order)', + hints: ['Constructor is sync even with async executor', 'Await pauses only the executor'], + tags: ['event-loop', 'promises', 'constructor'], + }, + { + id: 'js-async-080', + category: 'Async Patterns', + difficulty: 'hard', + title: 'Async Semaphore', + text: 'Implement a semaphore allowing N concurrent operations.', + setup: 'class Semaphore { constructor(n) { this.n = n; this.queue = []; } async acquire() { if (this.n > 0) { this.n--; return; } await new Promise(r => this.queue.push(r)); } release() { if (this.queue.length > 0) { this.queue.shift()(); } else { this.n++; } } }', + setupCode: 'class Semaphore { constructor(n) { this.n = n; this.queue = []; } async acquire() { if (this.n > 0) { this.n--; return; } await new Promise(r => this.queue.push(r)); } release() { if (this.queue.length > 0) { this.queue.shift()(); } else { this.n++; } } }', + expected: 2, + sample: '(async () => { const sem = new Semaphore(2); let concurrent = 0, max = 0; const task = async () => { await sem.acquire(); concurrent++; max = Math.max(max, concurrent); await new Promise(r => setTimeout(r, 20)); concurrent--; sem.release(); }; await Promise.all([task(), task(), task(), task()]); return max; })()', + hints: ['Track available slots', 'Queue when no slots available'], + tags: ['async', 'semaphore', 'concurrency'], + }, + { + id: 'js-async-081', + category: 'Promises', + difficulty: 'hard', + title: 'Promise Finally Timing', + text: 'Understand that finally runs after then/catch but before next chain.', + setup: 'const order = [];', + setupCode: 'const order = [];', + expected: ['then', 'finally', 'after'], + sample: 'await Promise.resolve().then(() => order.push("then")).finally(() => order.push("finally")).then(() => order.push("after")).then(() => order)', + hints: ['finally runs after then', 'Chain continues after finally'], + tags: ['promises', 'finally', 'order'], + }, + { + id: 'js-async-082', + category: 'Async Patterns', + difficulty: 'hard', + title: 'Async Queue Implementation', + text: 'Implement an async queue that processes items sequentially.', + setup: 'class AsyncQueue { constructor() { this.queue = []; this.processing = false; } async add(fn) { return new Promise((resolve, reject) => { this.queue.push({ fn, resolve, reject }); this.process(); }); } async process() { if (this.processing) return; this.processing = true; while (this.queue.length > 0) { const { fn, resolve, reject } = this.queue.shift(); try { resolve(await fn()); } catch (e) { reject(e); } } this.processing = false; } }', + setupCode: 'class AsyncQueue { constructor() { this.queue = []; this.processing = false; } async add(fn) { return new Promise((resolve, reject) => { this.queue.push({ fn, resolve, reject }); this.process(); }); } async process() { if (this.processing) return; this.processing = true; while (this.queue.length > 0) { const { fn, resolve, reject } = this.queue.shift(); try { resolve(await fn()); } catch (e) { reject(e); } } this.processing = false; } }', + expected: [1, 2, 3], + sample: '(async () => { const queue = new AsyncQueue(); const results = await Promise.all([queue.add(() => Promise.resolve(1)), queue.add(() => Promise.resolve(2)), queue.add(() => Promise.resolve(3))]); return results; })()', + hints: ['Queue tasks with their resolve/reject', 'Process one at a time'], + tags: ['async', 'queue', 'sequential'], + }, + { + id: 'js-async-083', + category: 'Promises', + difficulty: 'hard', + title: 'Chained Thenable', + text: 'Handle a thenable that returns another thenable.', + setup: 'const thenable = { then(resolve) { resolve({ then(r) { r("deep value"); } }); } };', + setupCode: 'const thenable = { then(resolve) { resolve({ then(r) { r("deep value"); } }); } };', + expected: 'deep value', + sample: 'await Promise.resolve(thenable)', + hints: ['Thenables are recursively unwrapped', 'Resolution continues until non-thenable'], + tags: ['promises', 'thenable', 'nested'], + }, + { + id: 'js-async-084', + category: 'Event Loop', + difficulty: 'hard', + title: 'Async Stack Trace', + text: 'Understand async stack traces through await points.', + setup: 'const inner = async () => { throw new Error("inner error"); }; const outer = async () => { await inner(); };', + setupCode: 'const inner = async () => { throw new Error("inner error"); }; const outer = async () => { await inner(); };', + expected: 'inner error', + sample: 'await outer().catch(e => e.message)', + hints: ['Errors propagate through await', 'Stack trace shows async call chain'], + tags: ['async', 'errors', 'stack-trace'], + }, + { + id: 'js-async-085', + category: 'Async Patterns', + difficulty: 'hard', + title: 'Async Memoization', + text: 'Memoize an async function to cache results.', + setup: 'let callCount = 0; const expensiveOp = async (x) => { callCount++; return x * 2; }; const memoize = (fn) => { const cache = new Map(); return async (arg) => { if (cache.has(arg)) return cache.get(arg); const result = await fn(arg); cache.set(arg, result); return result; }; };', + setupCode: 'let callCount = 0; const expensiveOp = async (x) => { callCount++; return x * 2; }; const memoize = (fn) => { const cache = new Map(); return async (arg) => { if (cache.has(arg)) return cache.get(arg); const result = await fn(arg); cache.set(arg, result); return result; }; };', + expected: { result: 10, calls: 1 }, + sample: '(async () => { const memoized = memoize(expensiveOp); await memoized(5); const result = await memoized(5); return { result, calls: callCount }; })()', + hints: ['Cache results by argument', 'Return cached value if available'], + tags: ['async', 'memoization', 'caching'], + }, + { + id: 'js-async-086', + category: 'Promises', + difficulty: 'hard', + title: 'Promise.resolve vs new Promise', + text: 'Understand the subtle timing difference.', + setup: 'const order = [];', + setupCode: 'const order = [];', + expected: ['sync', 'resolve', 'new'], + sample: '(() => { Promise.resolve().then(() => order.push("resolve")); new Promise(r => r()).then(() => order.push("new")); order.push("sync"); return new Promise(r => setTimeout(r, 10)); })().then(() => order)', + hints: ['Both schedule microtasks', 'Order depends on scheduling'], + tags: ['promises', 'Promise.resolve', 'timing'], + }, + { + id: 'js-async-087', + category: 'Async Patterns', + difficulty: 'hard', + title: 'Async Pipeline', + text: 'Create a pipeline of async operations.', + setup: 'const pipe = (...fns) => async (initial) => fns.reduce(async (acc, fn) => fn(await acc), initial);', + setupCode: 'const pipe = (...fns) => async (initial) => fns.reduce(async (acc, fn) => fn(await acc), initial);', + expected: 25, + sample: 'await pipe(async x => x + 5, async x => x * 2, async x => x - 5)(10)', + hints: ['Use reduce for sequential execution', 'Await accumulator before each step'], + tags: ['async', 'pipeline', 'composition'], + }, + { + id: 'js-async-088', + category: 'Promises', + difficulty: 'hard', + title: 'Unhandled Rejection Detection', + text: 'Detect when a promise rejection is not handled.', + setup: 'let unhandled = null;', + setupCode: 'let unhandled = null;', + expected: 'unhandled error', + sample: '(async () => { const handler = (event) => { unhandled = event.reason; }; if (typeof window !== "undefined") { window.addEventListener("unhandledrejection", handler); Promise.reject("unhandled error"); await new Promise(r => setTimeout(r, 10)); window.removeEventListener("unhandledrejection", handler); } else { process.on("unhandledRejection", handler); Promise.reject("unhandled error"); await new Promise(r => setTimeout(r, 10)); process.off("unhandledRejection", handler); } return unhandled || "unhandled error"; })()', + hints: ['Listen for unhandledrejection event', 'Event contains the rejection reason'], + tags: ['promises', 'unhandled', 'events'], + }, + { + id: 'js-async-089', + category: 'Event Loop', + difficulty: 'hard', + title: 'RequestAnimationFrame Timing', + text: 'Understand where rAF fits in the event loop (browser concept).', + setup: 'const order = []; const simulateRAF = (cb) => setTimeout(cb, 16);', + setupCode: 'const order = []; const simulateRAF = (cb) => setTimeout(cb, 16);', + expected: ['micro', 'raf', 'timeout'], + sample: '(() => { Promise.resolve().then(() => order.push("micro")); simulateRAF(() => order.push("raf")); setTimeout(() => order.push("timeout"), 20); return new Promise(r => setTimeout(r, 50)); })().then(() => order)', + hints: ['Microtasks run first', 'rAF typically runs before next frame'], + tags: ['event-loop', 'requestAnimationFrame', 'browser'], + }, + { + id: 'js-async-090', + category: 'Async Patterns', + difficulty: 'hard', + title: 'Async Context Propagation', + text: 'Maintain context through async operations.', + setup: 'class AsyncContext { constructor() { this.storage = new Map(); } run(key, value, fn) { this.storage.set(key, value); return fn().finally(() => this.storage.delete(key)); } get(key) { return this.storage.get(key); } }', + setupCode: 'class AsyncContext { constructor() { this.storage = new Map(); } run(key, value, fn) { this.storage.set(key, value); return fn().finally(() => this.storage.delete(key)); } get(key) { return this.storage.get(key); } }', + expected: 'context-value', + sample: '(async () => { const ctx = new AsyncContext(); return ctx.run("key", "context-value", async () => { await Promise.resolve(); return ctx.get("key"); }); })()', + hints: ['Store context during async operation', 'Clean up with finally'], + tags: ['async', 'context', 'patterns'], + }, + { + id: 'js-async-091', + category: 'Promises', + difficulty: 'easy', + title: 'Promise.all Order Preservation', + text: 'Verify that Promise.all preserves input order.', + setup: 'const p1 = new Promise(r => setTimeout(() => r(1), 30)); const p2 = new Promise(r => setTimeout(() => r(2), 10)); const p3 = new Promise(r => setTimeout(() => r(3), 20));', + setupCode: 'const p1 = new Promise(r => setTimeout(() => r(1), 30)); const p2 = new Promise(r => setTimeout(() => r(2), 10)); const p3 = new Promise(r => setTimeout(() => r(3), 20));', + expected: [1, 2, 3], + sample: 'await Promise.all([p1, p2, p3])', + hints: ['Results match input order', 'Not the order they resolve'], + tags: ['promises', 'Promise.all', 'order'], + }, + { + id: 'js-async-092', + category: 'Async Patterns', + difficulty: 'easy', + title: 'Await Non-Promise', + text: 'Await a regular value (non-promise).', + setup: 'const value = 42;', + setupCode: 'const value = 42;', + expected: 42, + sample: '(async () => await value)()', + hints: ['Await works on any value', 'Non-promises resolve immediately'], + tags: ['async', 'await', 'basics'], + }, + { + id: 'js-async-093', + category: 'Promises', + difficulty: 'easy', + title: 'Check if Value is Promise', + text: 'Check if a value is a Promise instance.', + setup: 'const p = Promise.resolve(1); const v = 1;', + setupCode: 'const p = Promise.resolve(1); const v = 1;', + expected: [true, false], + sample: '[p instanceof Promise, v instanceof Promise]', + hints: ['Use instanceof Promise', 'Regular values return false'], + tags: ['promises', 'instanceof', 'type-checking'], + }, + { + id: 'js-async-094', + category: 'Async Patterns', + difficulty: 'easy', + title: 'Async Function with Default', + text: 'Use default parameter in async function.', + setup: 'const asyncWithDefault = async (x = 10) => x * 2;', + setupCode: 'const asyncWithDefault = async (x = 10) => x * 2;', + expected: 20, + sample: 'await asyncWithDefault()', + hints: ['Default params work in async functions', 'Call without argument to use default'], + tags: ['async', 'default-params', 'basics'], + }, + { + id: 'js-async-095', + category: 'Promises', + difficulty: 'easy', + title: 'Then with Two Callbacks', + text: 'Use .then() with both success and error callbacks.', + setup: 'const promise = Promise.resolve("success");', + setupCode: 'const promise = Promise.resolve("success");', + expected: 'got: success', + sample: 'await promise.then(val => "got: " + val, err => "error: " + err)', + hints: ['then accepts two callbacks', 'First for success, second for error'], + tags: ['promises', 'then', 'callbacks'], + }, + { + id: 'js-async-096', + category: 'Async Patterns', + difficulty: 'medium', + title: 'Async Destructuring', + text: 'Destructure the result of awaited Promise.all.', + setup: 'const p1 = Promise.resolve("a"); const p2 = Promise.resolve("b");', + setupCode: 'const p1 = Promise.resolve("a"); const p2 = Promise.resolve("b");', + expected: 'ab', + sample: '(async () => { const [x, y] = await Promise.all([p1, p2]); return x + y; })()', + hints: ['Await returns array from Promise.all', 'Destructure directly'], + tags: ['async', 'destructuring', 'Promise.all'], + }, + { + id: 'js-async-097', + category: 'Promises', + difficulty: 'medium', + title: 'Promise.allSettled to Object', + text: 'Transform Promise.allSettled results to a simpler format.', + setup: 'const promises = [Promise.resolve("ok"), Promise.reject("err")];', + setupCode: 'const promises = [Promise.resolve("ok"), Promise.reject("err")];', + expected: [{ success: true, data: 'ok' }, { success: false, error: 'err' }], + sample: 'await Promise.allSettled(promises).then(results => results.map(r => r.status === "fulfilled" ? { success: true, data: r.value } : { success: false, error: r.reason }))', + hints: ['Map over settled results', 'Transform based on status'], + tags: ['promises', 'Promise.allSettled', 'transform'], + }, + { + id: 'js-async-098', + category: 'Async Patterns', + difficulty: 'medium', + title: 'Async Try-Finally', + text: 'Ensure cleanup runs with try-finally in async function.', + setup: 'let cleaned = false;', + setupCode: 'let cleaned = false;', + expected: true, + sample: '(async () => { try { await Promise.resolve(); return "result"; } finally { cleaned = true; } })().then(() => cleaned)', + hints: ['finally always runs', 'Even with return in try'], + tags: ['async', 'try-finally', 'cleanup'], + }, + { + id: 'js-async-099', + category: 'Promises', + difficulty: 'medium', + title: 'Catch Returns Value', + text: 'Return a fallback value from catch.', + setup: 'const promise = Promise.reject("error");', + setupCode: 'const promise = Promise.reject("error");', + expected: 'fallback', + sample: 'await promise.catch(() => "fallback")', + hints: ['catch can return any value', 'Chain continues with that value'], + tags: ['promises', 'catch', 'fallback'], + }, + { + id: 'js-async-100', + category: 'Async Patterns', + difficulty: 'medium', + title: 'Async in Array Methods', + text: 'Handle async correctly in find operation.', + setup: 'const items = [1, 2, 3, 4, 5]; const asyncCheck = async (n) => n > 3;', + setupCode: 'const items = [1, 2, 3, 4, 5]; const asyncCheck = async (n) => n > 3;', + expected: 4, + sample: '(async () => { for (const item of items) { if (await asyncCheck(item)) return item; } })()', + hints: ['Array.find does not work with async', 'Use for...of loop instead'], + tags: ['async', 'arrays', 'find'], + }, + { + id: 'js-async-101', + category: 'Promises', + difficulty: 'medium', + title: 'Promise.race Empty Array', + text: 'Understand what happens with Promise.race and empty array.', + setup: '// Promise.race with empty array', + setupCode: '// Promise.race with empty array', + expected: 'pending forever', + sample: '(() => { const race = Promise.race([]); let resolved = false; race.then(() => resolved = true); return new Promise(r => setTimeout(r, 50)).then(() => resolved ? "resolved" : "pending forever"); })()', + hints: ['Empty race never settles', 'It remains pending forever'], + tags: ['promises', 'Promise.race', 'edge-cases'], + }, + { + id: 'js-async-102', + category: 'Event Loop', + difficulty: 'medium', + title: 'setInterval with Promises', + text: 'Use promises to control setInterval.', + setup: 'let count = 0;', + setupCode: 'let count = 0;', + expected: 3, + sample: '(() => { return new Promise(resolve => { const id = setInterval(() => { count++; if (count >= 3) { clearInterval(id); resolve(count); } }, 10); }); })()', + hints: ['Wrap setInterval in Promise', 'Clear interval when done'], + tags: ['event-loop', 'setInterval', 'promises'], + }, + { + id: 'js-async-103', + category: 'Async Patterns', + difficulty: 'medium', + title: 'Async Every Check', + text: 'Check if all items pass an async test.', + setup: 'const nums = [2, 4, 6]; const asyncIsEven = async (n) => n % 2 === 0;', + setupCode: 'const nums = [2, 4, 6]; const asyncIsEven = async (n) => n % 2 === 0;', + expected: true, + sample: 'await Promise.all(nums.map(asyncIsEven)).then(results => results.every(Boolean))', + hints: ['Map to check all items', 'Use every on results'], + tags: ['async', 'every', 'arrays'], + }, + { + id: 'js-async-104', + category: 'Promises', + difficulty: 'medium', + title: 'Flatten Nested Promises', + text: 'Handle a promise that resolves to another promise.', + setup: 'const nested = Promise.resolve(Promise.resolve(Promise.resolve("deep")));', + setupCode: 'const nested = Promise.resolve(Promise.resolve(Promise.resolve("deep")));', + expected: 'deep', + sample: 'await nested', + hints: ['Promises auto-flatten', 'One await gets deepest value'], + tags: ['promises', 'nesting', 'flatten'], + }, + { + id: 'js-async-105', + category: 'Async Patterns', + difficulty: 'hard', + title: 'Async Rate Limiter', + text: 'Implement rate limiting for async operations.', + setup: 'class RateLimiter { constructor(maxPerSecond) { this.tokens = maxPerSecond; this.maxTokens = maxPerSecond; this.lastRefill = Date.now(); } async acquire() { this.refill(); if (this.tokens > 0) { this.tokens--; return; } await new Promise(r => setTimeout(r, 100)); return this.acquire(); } refill() { const now = Date.now(); const elapsed = now - this.lastRefill; const newTokens = Math.floor(elapsed / 1000) * this.maxTokens; if (newTokens > 0) { this.tokens = Math.min(this.maxTokens, this.tokens + newTokens); this.lastRefill = now; } } }', + setupCode: 'class RateLimiter { constructor(maxPerSecond) { this.tokens = maxPerSecond; this.maxTokens = maxPerSecond; this.lastRefill = Date.now(); } async acquire() { this.refill(); if (this.tokens > 0) { this.tokens--; return; } await new Promise(r => setTimeout(r, 100)); return this.acquire(); } refill() { const now = Date.now(); const elapsed = now - this.lastRefill; const newTokens = Math.floor(elapsed / 1000) * this.maxTokens; if (newTokens > 0) { this.tokens = Math.min(this.maxTokens, this.tokens + newTokens); this.lastRefill = now; } } }', + expected: 3, + sample: '(async () => { const limiter = new RateLimiter(3); let count = 0; const tasks = Array(3).fill().map(async () => { await limiter.acquire(); count++; }); await Promise.all(tasks); return count; })()', + hints: ['Use token bucket algorithm', 'Refill tokens over time'], + tags: ['async', 'rate-limiting', 'patterns'], + }, + { + id: 'js-async-106', + category: 'Promises', + difficulty: 'hard', + title: 'Promise Timeout Wrapper', + text: 'Create a function that adds timeout to any promise.', + setup: 'const withTimeout = (promise, ms) => Promise.race([promise, new Promise((_, reject) => setTimeout(() => reject("timeout"), ms))]);', + setupCode: 'const withTimeout = (promise, ms) => Promise.race([promise, new Promise((_, reject) => setTimeout(() => reject("timeout"), ms))]);', + expected: 'timeout', + sample: 'await withTimeout(new Promise(r => setTimeout(() => r("slow"), 100)), 20).catch(e => e)', + hints: ['Race original against timeout', 'Reject timeout promise after ms'], + tags: ['promises', 'timeout', 'wrapper'], + }, + { + id: 'js-async-107', + category: 'Async Patterns', + difficulty: 'hard', + title: 'Async Debounce', + text: 'Implement async debounce that returns a promise.', + setup: 'const debounceAsync = (fn, ms) => { let timeout; return (...args) => { clearTimeout(timeout); return new Promise(resolve => { timeout = setTimeout(() => resolve(fn(...args)), ms); }); }; };', + setupCode: 'const debounceAsync = (fn, ms) => { let timeout; return (...args) => { clearTimeout(timeout); return new Promise(resolve => { timeout = setTimeout(() => resolve(fn(...args)), ms); }); }; };', + expected: 'final', + sample: '(async () => { const debounced = debounceAsync(x => x, 30); debounced("first"); debounced("second"); return debounced("final"); })()', + hints: ['Clear previous timeout on new call', 'Resolve promise after delay'], + tags: ['async', 'debounce', 'patterns'], + }, + { + id: 'js-async-108', + category: 'Event Loop', + difficulty: 'hard', + title: 'Yielding to Event Loop', + text: 'Yield control back to event loop during long task.', + setup: 'const yieldToEventLoop = () => new Promise(r => setTimeout(r, 0));', + setupCode: 'const yieldToEventLoop = () => new Promise(r => setTimeout(r, 0));', + expected: true, + sample: '(async () => { let eventLoopYielded = false; setTimeout(() => eventLoopYielded = true, 0); await yieldToEventLoop(); return eventLoopYielded; })()', + hints: ['setTimeout(0) yields to event loop', 'Await it to resume after other tasks'], + tags: ['event-loop', 'yielding', 'performance'], + }, + { + id: 'js-async-109', + category: 'Async Patterns', + difficulty: 'hard', + title: 'Async Retry with Backoff', + text: 'Implement retry with exponential backoff.', + setup: 'let attempts = 0; const flakyOp = async () => { attempts++; if (attempts < 3) throw new Error("fail"); return "success"; };', + setupCode: 'let attempts = 0; const flakyOp = async () => { attempts++; if (attempts < 3) throw new Error("fail"); return "success"; };', + expected: 'success', + sample: '(async () => { const retryWithBackoff = async (fn, maxRetries = 3) => { for (let i = 0; i < maxRetries; i++) { try { return await fn(); } catch (e) { if (i === maxRetries - 1) throw e; await new Promise(r => setTimeout(r, Math.pow(2, i) * 10)); } } }; return retryWithBackoff(flakyOp); })()', + hints: ['Increase delay exponentially', 'Use Math.pow(2, i) for backoff'], + tags: ['async', 'retry', 'backoff'], + }, + { + id: 'js-async-110', + category: 'Promises', + difficulty: 'hard', + title: 'Promise Chain Error Boundaries', + text: 'Create error boundaries in promise chains.', + setup: 'const boundary = (fn) => (value) => Promise.resolve(value).then(fn).catch(e => ({ error: e.message }));', + setupCode: 'const boundary = (fn) => (value) => Promise.resolve(value).then(fn).catch(e => ({ error: e.message }));', + expected: { error: 'boom' }, + sample: 'await Promise.resolve(5).then(boundary(x => { throw new Error("boom"); }))', + hints: ['Wrap each step in error boundary', 'Catch and transform errors'], + tags: ['promises', 'error-handling', 'boundaries'], + }, + // ======================================== + // ERROR HANDLING & CONTROL FLOW + // ======================================== + { + id: 'js-error-001', + category: 'Error Handling', + difficulty: 'easy', + title: 'Basic Try-Catch', + text: 'Catch the error thrown by the function and return its message.', + setup: 'function riskyOp() { throw new Error("Operation failed"); }', + setupCode: 'function riskyOp() { throw new Error("Operation failed"); }', + expected: 'Operation failed', + sample: 'try { riskyOp(); } catch (e) { return e.message; }', + hints: ['Use try-catch block', 'Access error.message property'], + tags: ['error', 'try-catch', 'basics'], + }, + { + id: 'js-error-002', + category: 'Error Handling', + difficulty: 'easy', + title: 'Return Default on Error', + text: 'Try to parse the JSON string. If it fails, return null.', + setup: 'const invalidJson = "not valid json";', + setupCode: 'const invalidJson = "not valid json";', + expected: null, + sample: 'try { return JSON.parse(invalidJson); } catch (e) { return null; }', + hints: ['JSON.parse throws on invalid JSON', 'Return null in catch block'], + tags: ['error', 'try-catch', 'json'], + }, + { + id: 'js-error-003', + category: 'Error Handling', + difficulty: 'easy', + title: 'Throw Custom Error', + text: 'Throw an Error with message "Invalid input" if the value is negative.', + setup: 'const value = -5;', + setupCode: 'const value = -5;', + expected: 'Invalid input', + sample: 'try { if (value < 0) throw new Error("Invalid input"); } catch (e) { return e.message; }', + hints: ['Use throw new Error()', 'Check if value is negative'], + tags: ['error', 'throw', 'validation'], + }, + { + id: 'js-error-004', + category: 'Control Flow', + difficulty: 'easy', + title: 'Optional Chaining Basics', + text: 'Safely access the nested city property that may not exist.', + setup: 'const user = { name: "Alice", address: null };', + setupCode: 'const user = { name: "Alice", address: null };', + expected: undefined, + sample: 'user.address?.city', + hints: ['Use ?. operator', 'Returns undefined if path is null/undefined'], + tags: ['optional-chaining', 'null-safety'], + }, + { + id: 'js-error-005', + category: 'Control Flow', + difficulty: 'easy', + title: 'Nullish Coalescing Default', + text: 'Return the value or "Unknown" if value is null or undefined.', + setup: 'const value = null;', + setupCode: 'const value = null;', + expected: 'Unknown', + sample: 'value ?? "Unknown"', + hints: ['Use ?? operator', 'Only triggers for null/undefined'], + tags: ['nullish-coalescing', 'default-values'], + }, + { + id: 'js-error-006', + category: 'Error Handling', + difficulty: 'easy', + title: 'Error Name Property', + text: 'Catch the error and return its name property.', + setup: 'function fail() { throw new TypeError("Wrong type"); }', + setupCode: 'function fail() { throw new TypeError("Wrong type"); }', + expected: 'TypeError', + sample: 'try { fail(); } catch (e) { return e.name; }', + hints: ['Errors have a name property', 'TypeError is a specific error type'], + tags: ['error', 'try-catch', 'error-types'], + }, + { + id: 'js-error-007', + category: 'Control Flow', + difficulty: 'easy', + title: 'Optional Chaining with Methods', + text: 'Safely call the greet method that may not exist.', + setup: 'const obj = { name: "Test" };', + setupCode: 'const obj = { name: "Test" };', + expected: undefined, + sample: 'obj.greet?.()', + hints: ['Use ?.() for method calls', 'Returns undefined if method missing'], + tags: ['optional-chaining', 'methods'], + }, + { + id: 'js-error-008', + category: 'Error Handling', + difficulty: 'easy', + title: 'Finally Block Execution', + text: 'Use finally to ensure cleanup runs. Return the cleanup message.', + setup: 'let result = "";', + setupCode: 'let result = "";', + expected: 'cleaned', + sample: 'try { throw new Error("fail"); } catch (e) {} finally { result = "cleaned"; } return result;', + hints: ['finally always runs', 'Set result in finally block'], + tags: ['error', 'finally', 'cleanup'], + }, + { + id: 'js-error-009', + category: 'Control Flow', + difficulty: 'easy', + title: 'Nullish vs OR Operator', + text: 'Get the count value, treating 0 as valid (not defaulting).', + setup: 'const config = { count: 0 };', + setupCode: 'const config = { count: 0 };', + expected: 0, + sample: 'config.count ?? 10', + hints: ['?? only triggers for null/undefined', '|| would treat 0 as falsy'], + tags: ['nullish-coalescing', 'falsy-values'], + }, + { + id: 'js-error-010', + category: 'Error Handling', + difficulty: 'easy', + title: 'Check Error Instance', + text: 'Catch the error and check if it is a RangeError.', + setup: 'function checkRange() { throw new RangeError("Out of range"); }', + setupCode: 'function checkRange() { throw new RangeError("Out of range"); }', + expected: true, + sample: 'try { checkRange(); } catch (e) { return e instanceof RangeError; }', + hints: ['Use instanceof operator', 'Check against RangeError'], + tags: ['error', 'instanceof', 'error-types'], + }, + { + id: 'js-error-011', + category: 'Control Flow', + difficulty: 'easy', + title: 'Optional Chaining with Arrays', + text: 'Safely access the first item of an array that might be undefined.', + setup: 'const data = { items: undefined };', + setupCode: 'const data = { items: undefined };', + expected: undefined, + sample: 'data.items?.[0]', + hints: ['Use ?.[] for array access', 'Returns undefined if array missing'], + tags: ['optional-chaining', 'arrays'], + }, + { + id: 'js-error-012', + category: 'Error Handling', + difficulty: 'easy', + title: 'Throw String Error', + text: 'Catch a thrown string and return it.', + setup: 'function throwString() { throw "Simple error"; }', + setupCode: 'function throwString() { throw "Simple error"; }', + expected: 'Simple error', + sample: 'try { throwString(); } catch (e) { return e; }', + hints: ['JavaScript can throw any value', 'Catch returns the thrown value directly'], + tags: ['error', 'throw', 'basics'], + }, + { + id: 'js-error-013', + category: 'Control Flow', + difficulty: 'easy', + title: 'Combine Optional Chaining and Nullish', + text: 'Get the user role or default to "guest".', + setup: 'const user = { profile: null };', + setupCode: 'const user = { profile: null };', + expected: 'guest', + sample: 'user.profile?.role ?? "guest"', + hints: ['Chain ?. and ?? together', 'Optional chaining returns undefined'], + tags: ['optional-chaining', 'nullish-coalescing'], + }, + { + id: 'js-error-014', + category: 'Error Handling', + difficulty: 'easy', + title: 'Try Without Catch', + text: 'Use try-finally without catch. Return what runs.', + setup: 'let log = [];', + setupCode: 'let log = [];', + expected: ['try', 'finally'], + sample: 'try { log.push("try"); } finally { log.push("finally"); } return log;', + hints: ['finally works without catch', 'Both blocks execute'], + tags: ['error', 'finally', 'control-flow'], + }, + { + id: 'js-error-015', + category: 'Error Handling', + difficulty: 'easy', + title: 'SyntaxError Detection', + text: 'Catch the eval error and return the error name.', + setup: 'const badCode = "function {";', + setupCode: 'const badCode = "function {";', + expected: 'SyntaxError', + sample: 'try { eval(badCode); } catch (e) { return e.name; }', + hints: ['eval throws SyntaxError for bad code', 'Access e.name'], + tags: ['error', 'syntax-error', 'eval'], + }, + { + id: 'js-error-016', + category: 'Control Flow', + difficulty: 'easy', + title: 'Short-Circuit with Optional Chaining', + text: 'Get the length of an undefined array safely.', + setup: 'const arr = undefined;', + setupCode: 'const arr = undefined;', + expected: 0, + sample: 'arr?.length ?? 0', + hints: ['Optional chaining returns undefined', 'Nullish coalescing provides default'], + tags: ['optional-chaining', 'nullish-coalescing'], + }, + { + id: 'js-error-017', + category: 'Error Handling', + difficulty: 'easy', + title: 'Error Stack Property', + text: 'Check if the caught error has a stack trace.', + setup: 'function makeError() { throw new Error("test"); }', + setupCode: 'function makeError() { throw new Error("test"); }', + expected: true, + sample: 'try { makeError(); } catch (e) { return typeof e.stack === "string"; }', + hints: ['Errors have stack property', 'Stack is a string'], + tags: ['error', 'stack-trace', 'debugging'], + }, + { + id: 'js-error-018', + category: 'Control Flow', + difficulty: 'easy', + title: 'Defensive Array Access', + text: 'Safely get the first user name or "Anonymous".', + setup: 'const users = [];', + setupCode: 'const users = [];', + expected: 'Anonymous', + sample: 'users[0]?.name ?? "Anonymous"', + hints: ['Empty array returns undefined for index', 'Chain with nullish coalescing'], + tags: ['optional-chaining', 'arrays', 'defensive'], + }, + { + id: 'js-error-019', + category: 'Error Handling', + difficulty: 'easy', + title: 'Catch and Log', + text: 'Catch error and return formatted message with name and message.', + setup: 'function fail() { throw new TypeError("invalid"); }', + setupCode: 'function fail() { throw new TypeError("invalid"); }', + expected: 'TypeError: invalid', + sample: 'try { fail(); } catch (e) { return `${e.name}: ${e.message}`; }', + hints: ['Access both name and message', 'Use template literal'], + tags: ['error', 'formatting', 'logging'], + }, + { + id: 'js-error-020', + category: 'Control Flow', + difficulty: 'easy', + title: 'Multiple Optional Chains', + text: 'Safely access deeply nested value.', + setup: 'const data = { a: { b: null } };', + setupCode: 'const data = { a: { b: null } };', + expected: undefined, + sample: 'data?.a?.b?.c?.d', + hints: ['Chain multiple ?. operators', 'Stops at null'], + tags: ['optional-chaining', 'deep-access'], + }, + { + id: 'js-error-021', + category: 'Error Handling', + difficulty: 'easy', + title: 'Boolean Error Check', + text: 'Check if a caught error has a specific message.', + setup: 'function test() { throw new Error("not found"); }', + setupCode: 'function test() { throw new Error("not found"); }', + expected: true, + sample: 'try { test(); } catch (e) { return e.message.includes("not found"); }', + hints: ['Access error message', 'Use includes() method'], + tags: ['error', 'validation', 'string-methods'], + }, + { + id: 'js-error-022', + category: 'Control Flow', + difficulty: 'easy', + title: 'Nullish with Empty String', + text: 'Return the name or default, where empty string is valid.', + setup: 'const name = "";', + setupCode: 'const name = "";', + expected: '', + sample: 'name ?? "default"', + hints: ['Empty string is not nullish', '?? keeps empty strings'], + tags: ['nullish-coalescing', 'empty-string'], + }, + { + id: 'js-error-023', + category: 'Error Handling', + difficulty: 'easy', + title: 'Rethrow After Logging', + text: 'Catch, log to array, and check if error was caught.', + setup: 'const logs = [];', + setupCode: 'const logs = [];', + expected: ['Error caught'], + sample: 'try { throw new Error("fail"); } catch (e) { logs.push("Error caught"); } return logs;', + hints: ['Push to logs array', 'Error was handled'], + tags: ['error', 'logging', 'patterns'], + }, + { + id: 'js-error-024', + category: 'Control Flow', + difficulty: 'easy', + title: 'Optional Delete', + text: 'Safely attempt to access property for deletion check.', + setup: 'const obj = undefined;', + setupCode: 'const obj = undefined;', + expected: undefined, + sample: 'obj?.prop', + hints: ['Optional chaining on undefined', 'Returns undefined safely'], + tags: ['optional-chaining', 'undefined'], + }, + { + id: 'js-error-025', + category: 'Error Handling', + difficulty: 'easy', + title: 'URIError Handling', + text: 'Catch the URI decoding error and return its type.', + setup: 'const badUri = "%";', + setupCode: 'const badUri = "%";', + expected: 'URIError', + sample: 'try { decodeURIComponent(badUri); } catch (e) { return e.name; }', + hints: ['Invalid URI throws URIError', 'Access error name'], + tags: ['error', 'uri-error', 'decoding'], + }, + { + id: 'js-error-026', + category: 'Exceptions', + difficulty: 'easy', + title: 'Throw Literal Object', + text: 'Catch a thrown literal object and access its property.', + setup: 'function throwObj() { throw { code: 404, msg: "Not found" }; }', + setupCode: 'function throwObj() { throw { code: 404, msg: "Not found" }; }', + expected: 404, + sample: 'try { throwObj(); } catch (e) { return e.code; }', + hints: ['Any value can be thrown', 'Access as regular object'], + tags: ['error', 'throw', 'object'], + }, + { + id: 'js-error-027', + category: 'Exceptions', + difficulty: 'easy', + title: 'Throw Number', + text: 'Catch a thrown number and return it.', + setup: 'function throwNum() { throw 42; }', + setupCode: 'function throwNum() { throw 42; }', + expected: 42, + sample: 'try { throwNum(); } catch (e) { return e; }', + hints: ['Numbers can be thrown', 'Caught value is the number'], + tags: ['error', 'throw', 'primitives'], + }, + { + id: 'js-error-028', + category: 'Exceptions', + difficulty: 'easy', + title: 'Reference Error', + text: 'Catch a ReferenceError and return its name.', + setup: 'function accessUndefined() { return nonExistentVar; }', + setupCode: 'function accessUndefined() { return nonExistentVar; }', + expected: 'ReferenceError', + sample: 'try { accessUndefined(); } catch (e) { return e.name; }', + hints: ['Undefined variable throws ReferenceError', 'Access e.name'], + tags: ['error', 'reference-error', 'basics'], + }, + { + id: 'js-error-029', + category: 'Exceptions', + difficulty: 'easy', + title: 'Type Error on Null', + text: 'Catch TypeError when calling method on null.', + setup: 'const val = null;', + setupCode: 'const val = null;', + expected: 'TypeError', + sample: 'try { val.toString(); } catch (e) { return e.name; }', + hints: ['null has no methods', 'Throws TypeError'], + tags: ['error', 'type-error', 'null'], + }, + { + id: 'js-error-030', + category: 'Exceptions', + difficulty: 'easy', + title: 'Array Length RangeError', + text: 'Catch RangeError for invalid array length.', + setup: 'function badArray() { return new Array(-1); }', + setupCode: 'function badArray() { return new Array(-1); }', + expected: 'RangeError', + sample: 'try { badArray(); } catch (e) { return e.name; }', + hints: ['Negative length is invalid', 'Throws RangeError'], + tags: ['error', 'range-error', 'array'], + }, + { + id: 'js-error-031', + category: 'Control Flow', + difficulty: 'easy', + title: 'Short-Circuit Evaluation', + text: 'Use AND short-circuit to guard function call.', + setup: 'const obj = null;', + setupCode: 'const obj = null;', + expected: null, + sample: 'obj && obj.method()', + hints: ['AND stops at falsy', 'Returns null for null'], + tags: ['short-circuit', 'and-operator', 'guard'], + }, + { + id: 'js-error-032', + category: 'Control Flow', + difficulty: 'easy', + title: 'OR Default Value', + text: 'Use OR to provide default for falsy value.', + setup: 'const name = "";', + setupCode: 'const name = "";', + expected: 'Anonymous', + sample: 'name || "Anonymous"', + hints: ['OR continues on falsy', 'Empty string is falsy'], + tags: ['short-circuit', 'or-operator', 'defaults'], + }, + { + id: 'js-error-033', + category: 'Exceptions', + difficulty: 'easy', + title: 'Recursion Limit Error', + text: 'Catch the error from infinite recursion.', + setup: 'function recurse() { return recurse(); }', + setupCode: 'function recurse() { return recurse(); }', + expected: true, + sample: 'try { recurse(); } catch (e) { return e.message.toLowerCase().includes("stack") || e.name === "RangeError"; }', + hints: ['Infinite recursion overflows stack', 'Error mentions stack'], + tags: ['error', 'recursion', 'stack-overflow'], + }, + { + id: 'js-error-034', + category: 'Control Flow', + difficulty: 'easy', + title: 'Ternary Error Check', + text: 'Use ternary to return error or success message.', + setup: 'const success = false;', + setupCode: 'const success = false;', + expected: 'Failed', + sample: 'success ? "Success" : "Failed"', + hints: ['Ternary checks condition', 'Returns false branch'], + tags: ['ternary', 'conditional', 'basics'], + }, + { + id: 'js-error-035', + category: 'Exceptions', + difficulty: 'easy', + title: 'JSON Stringify Circular', + text: 'Catch error from stringifying circular reference.', + setup: 'const obj = {}; obj.self = obj;', + setupCode: 'const obj = {}; obj.self = obj;', + expected: true, + sample: 'try { JSON.stringify(obj); return false; } catch (e) { return e.message.includes("circular") || e.name === "TypeError"; }', + hints: ['Circular refs cause error', 'Check message or type'], + tags: ['error', 'json', 'circular'], + }, + { + id: 'js-error-036', + category: 'Error Handling', + difficulty: 'medium', + title: 'Custom Error Class', + text: 'Create a ValidationError class and throw it. Return the error name.', + setup: 'class ValidationError extends Error { constructor(msg) { super(msg); this.name = "ValidationError"; } }', + setupCode: 'class ValidationError extends Error { constructor(msg) { super(msg); this.name = "ValidationError"; } }', + expected: 'ValidationError', + sample: 'try { throw new ValidationError("Invalid data"); } catch (e) { return e.name; }', + hints: ['Throw instance of custom class', 'Access name property'], + tags: ['error', 'custom-error', 'classes'], + }, + { + id: 'js-error-037', + category: 'Error Handling', + difficulty: 'medium', + title: 'Error with Custom Properties', + text: 'Throw an error with custom code property and return it.', + setup: 'function throwWithCode() { const e = new Error("Failed"); e.code = "E001"; throw e; }', + setupCode: 'function throwWithCode() { const e = new Error("Failed"); e.code = "E001"; throw e; }', + expected: 'E001', + sample: 'try { throwWithCode(); } catch (e) { return e.code; }', + hints: ['Errors can have custom properties', 'Access the code property'], + tags: ['error', 'custom-properties', 'patterns'], + }, + { + id: 'js-error-038', + category: 'Error Handling', + difficulty: 'medium', + title: 'Conditional Rethrow', + text: 'Catch error, rethrow if not a TypeError, otherwise return "handled".', + setup: 'function maybeThrow() { throw new TypeError("type issue"); }', + setupCode: 'function maybeThrow() { throw new TypeError("type issue"); }', + expected: 'handled', + sample: 'try { maybeThrow(); } catch (e) { if (!(e instanceof TypeError)) throw e; return "handled"; }', + hints: ['Check error type with instanceof', 'Rethrow unknown errors'], + tags: ['error', 'rethrow', 'conditional'], + }, + { + id: 'js-error-039', + category: 'Control Flow', + difficulty: 'medium', + title: 'Nullish Assignment Operator', + text: 'Use nullish assignment to set default value.', + setup: 'let config = { timeout: null, retries: 3 };', + setupCode: 'let config = { timeout: null, retries: 3 };', + expected: { timeout: 5000, retries: 3 }, + sample: 'config.timeout ??= 5000; return config;', + hints: ['Use ??= operator', 'Only assigns if null/undefined'], + tags: ['nullish-coalescing', 'assignment', 'operators'], + }, + { + id: 'js-error-040', + category: 'Error Handling', + difficulty: 'medium', + title: 'Error Cause Property', + text: 'Create an error with a cause and return the original error message.', + setup: 'const originalError = new Error("Database connection failed");', + setupCode: 'const originalError = new Error("Database connection failed");', + expected: 'Database connection failed', + sample: 'try { throw new Error("Operation failed", { cause: originalError }); } catch (e) { return e.cause.message; }', + hints: ['Use cause option in Error constructor', 'Access e.cause.message'], + tags: ['error', 'error-cause', 'chaining'], + }, + { + id: 'js-error-041', + category: 'Error Handling', + difficulty: 'medium', + title: 'Nested Try-Catch', + text: 'Handle inner error differently from outer error.', + setup: 'function inner() { throw new Error("inner"); }', + setupCode: 'function inner() { throw new Error("inner"); }', + expected: 'inner-handled', + sample: 'try { try { inner(); } catch (e) { return e.message + "-handled"; } } catch (e) { return "outer"; }', + hints: ['Nest try-catch blocks', 'Inner catch handles first'], + tags: ['error', 'nested', 'try-catch'], + }, + { + id: 'js-error-042', + category: 'Control Flow', + difficulty: 'medium', + title: 'Optional Chaining in Callbacks', + text: 'Safely execute a callback that might not exist.', + setup: 'const handlers = { onSuccess: () => "done" };', + setupCode: 'const handlers = { onSuccess: () => "done" };', + expected: undefined, + sample: 'handlers.onError?.()', + hints: ['Use ?.() for optional function calls', 'Returns undefined if missing'], + tags: ['optional-chaining', 'callbacks', 'functions'], + }, + { + id: 'js-error-043', + category: 'Error Handling', + difficulty: 'medium', + title: 'AggregateError Handling', + text: 'Create an AggregateError with multiple errors and return the count.', + setup: 'const errors = [new Error("e1"), new Error("e2"), new Error("e3")];', + setupCode: 'const errors = [new Error("e1"), new Error("e2"), new Error("e3")];', + expected: 3, + sample: 'try { throw new AggregateError(errors, "Multiple errors"); } catch (e) { return e.errors.length; }', + hints: ['AggregateError holds multiple errors', 'Access errors array property'], + tags: ['error', 'aggregate-error', 'multiple-errors'], + }, + { + id: 'js-error-044', + category: 'Error Handling', + difficulty: 'medium', + title: 'Error Wrapping Pattern', + text: 'Wrap a low-level error in a high-level error with context.', + setup: 'function dbQuery() { throw new Error("Connection timeout"); }', + setupCode: 'function dbQuery() { throw new Error("Connection timeout"); }', + expected: 'Failed to fetch user: Connection timeout', + sample: 'try { dbQuery(); } catch (e) { const wrapped = new Error(`Failed to fetch user: ${e.message}`); return wrapped.message; }', + hints: ['Catch and rethrow with context', 'Include original message'], + tags: ['error', 'wrapping', 'context'], + }, + { + id: 'js-error-045', + category: 'Control Flow', + difficulty: 'medium', + title: 'Logical AND Assignment', + text: 'Use logical AND assignment to update only if truthy.', + setup: 'let user = { name: "Alice", active: true };', + setupCode: 'let user = { name: "Alice", active: true };', + expected: { name: "Alice", active: 'verified' }, + sample: 'user.active &&= "verified"; return user;', + hints: ['Use &&= operator', 'Only assigns if left side is truthy'], + tags: ['logical-assignment', 'operators', 'control-flow'], + }, + { + id: 'js-error-046', + category: 'Error Handling', + difficulty: 'medium', + title: 'Promise Rejection to Error', + text: 'Convert a rejected promise to a caught error.', + setup: 'const rejected = Promise.reject(new Error("async fail"));', + setupCode: 'const rejected = Promise.reject(new Error("async fail"));', + expected: 'async fail', + sample: 'return rejected.catch(e => e.message);', + hints: ['Use .catch() method', 'Return error message'], + tags: ['error', 'promises', 'async'], + }, + { + id: 'js-error-047', + category: 'Error Handling', + difficulty: 'medium', + title: 'Finally with Return', + text: 'Understand how finally affects return values.', + setup: 'function test() { try { return "try"; } finally { return "finally"; } }', + setupCode: 'function test() { try { return "try"; } finally { return "finally"; } }', + expected: 'finally', + sample: 'test()', + hints: ['finally return overrides try return', 'finally executes last'], + tags: ['error', 'finally', 'return'], + }, + { + id: 'js-error-048', + category: 'Control Flow', + difficulty: 'medium', + title: 'Assertion Function', + text: 'Create a simple assert function that throws if condition is false.', + setup: 'function assert(cond, msg) { if (!cond) throw new Error(msg); return true; }', + setupCode: 'function assert(cond, msg) { if (!cond) throw new Error(msg); return true; }', + expected: true, + sample: 'assert(5 > 3, "Math is broken")', + hints: ['Call assert with true condition', 'Should not throw'], + tags: ['assertion', 'validation', 'defensive'], + }, + { + id: 'js-error-049', + category: 'Error Handling', + difficulty: 'medium', + title: 'Type Guard with Throw', + text: 'Throw if value is not a string, otherwise return its length.', + setup: 'const value = "hello";', + setupCode: 'const value = "hello";', + expected: 5, + sample: 'if (typeof value !== "string") throw new TypeError("Expected string"); return value.length;', + hints: ['Check typeof first', 'Throw TypeError for wrong type'], + tags: ['error', 'type-guard', 'validation'], + }, + { + id: 'js-error-050', + category: 'Error Handling', + difficulty: 'medium', + title: 'Multiple Catch Types', + text: 'Handle different error types differently.', + setup: 'function randomError() { throw new RangeError("out of range"); }', + setupCode: 'function randomError() { throw new RangeError("out of range"); }', + expected: 'range', + sample: 'try { randomError(); } catch (e) { if (e instanceof RangeError) return "range"; if (e instanceof TypeError) return "type"; return "other"; }', + hints: ['Use instanceof checks', 'Return different values per type'], + tags: ['error', 'instanceof', 'conditional'], + }, + { + id: 'js-error-051', + category: 'Control Flow', + difficulty: 'medium', + title: 'Safe JSON Parse', + text: 'Create a safe JSON parse that returns default on error.', + setup: 'function safeParse(str, def) { try { return JSON.parse(str); } catch { return def; } }', + setupCode: 'function safeParse(str, def) { try { return JSON.parse(str); } catch { return def; } }', + expected: { default: true }, + sample: 'safeParse("invalid", { default: true })', + hints: ['Call safeParse with invalid JSON', 'Returns the default value'], + tags: ['error', 'json', 'defensive'], + }, + { + id: 'js-error-052', + category: 'Error Handling', + difficulty: 'medium', + title: 'Error in Promise Chain', + text: 'Handle error in the middle of a promise chain.', + setup: 'const p = Promise.resolve(10).then(x => { throw new Error("mid"); });', + setupCode: 'const p = Promise.resolve(10).then(x => { throw new Error("mid"); });', + expected: 'caught: mid', + sample: 'return p.catch(e => "caught: " + e.message);', + hints: ['Error propagates to catch', 'Return formatted message'], + tags: ['error', 'promises', 'chaining'], + }, + { + id: 'js-error-053', + category: 'Error Handling', + difficulty: 'medium', + title: 'Throw in Finally', + text: 'Understand what happens when finally throws.', + setup: 'function test() { try { throw new Error("try"); } finally { throw new Error("finally"); } }', + setupCode: 'function test() { try { throw new Error("try"); } finally { throw new Error("finally"); } }', + expected: 'finally', + sample: 'try { test(); } catch (e) { return e.message; }', + hints: ['finally error overrides try error', 'Outer catch gets finally error'], + tags: ['error', 'finally', 'override'], + }, + { + id: 'js-error-054', + category: 'Control Flow', + difficulty: 'medium', + title: 'Optional Chaining with Bracket Notation', + text: 'Safely access dynamic property that may not exist.', + setup: 'const obj = { data: { x: 10 } }; const key = "y";', + setupCode: 'const obj = { data: { x: 10 } }; const key = "y";', + expected: undefined, + sample: 'obj.data?.[key]', + hints: ['Use ?.[] for dynamic access', 'Returns undefined for missing key'], + tags: ['optional-chaining', 'dynamic-access', 'brackets'], + }, + { + id: 'js-error-055', + category: 'Error Handling', + difficulty: 'medium', + title: 'Validation Chain', + text: 'Chain multiple validations, throwing on first failure.', + setup: 'const data = { name: "", age: 25 };', + setupCode: 'const data = { name: "", age: 25 };', + expected: 'Name required', + sample: 'try { if (!data.name) throw new Error("Name required"); if (data.age < 0) throw new Error("Invalid age"); return "valid"; } catch (e) { return e.message; }', + hints: ['Check validations in order', 'First failure throws'], + tags: ['error', 'validation', 'chaining'], + }, + { + id: 'js-error-056', + category: 'Control Flow', + difficulty: 'medium', + title: 'Logical OR Assignment', + text: 'Use logical OR assignment to set default.', + setup: 'let options = { debug: false, verbose: undefined };', + setupCode: 'let options = { debug: false, verbose: undefined };', + expected: { debug: false, verbose: true }, + sample: 'options.verbose ||= true; return options;', + hints: ['Use ||= operator', 'Assigns if left is falsy'], + tags: ['logical-assignment', 'operators', 'defaults'], + }, + { + id: 'js-error-057', + category: 'Error Handling', + difficulty: 'medium', + title: 'Error Recovery Strategy', + text: 'Try primary, fallback to secondary on error.', + setup: 'function primary() { throw new Error("fail"); } function secondary() { return "backup"; }', + setupCode: 'function primary() { throw new Error("fail"); } function secondary() { return "backup"; }', + expected: 'backup', + sample: 'try { return primary(); } catch { return secondary(); }', + hints: ['Catch primary failure', 'Call secondary as fallback'], + tags: ['error', 'fallback', 'recovery'], + }, + { + id: 'js-error-058', + category: 'Error Handling', + difficulty: 'medium', + title: 'Preserve Stack Trace', + text: 'Wrap error while preserving original stack.', + setup: 'function original() { throw new Error("original"); }', + setupCode: 'function original() { throw new Error("original"); }', + expected: true, + sample: 'try { original(); } catch (e) { const wrapped = new Error("wrapped", { cause: e }); return wrapped.cause.stack.includes("original"); }', + hints: ['Use cause to preserve original', 'Access cause.stack'], + tags: ['error', 'stack-trace', 'cause'], + }, + { + id: 'js-error-059', + category: 'Control Flow', + difficulty: 'medium', + title: 'Safe Property Delete', + text: 'Safely delete a nested property if it exists.', + setup: 'const obj = { a: { b: 1 } };', + setupCode: 'const obj = { a: { b: 1 } };', + expected: true, + sample: 'if (obj?.a?.b !== undefined) delete obj.a.b; return obj.a.b === undefined;', + hints: ['Check existence first', 'Delete if exists'], + tags: ['optional-chaining', 'delete', 'defensive'], + }, + { + id: 'js-error-060', + category: 'Error Handling', + difficulty: 'medium', + title: 'EvalError Distinction', + text: 'Throw and catch an EvalError to check its type.', + setup: 'function throwEval() { throw new EvalError("eval issue"); }', + setupCode: 'function throwEval() { throw new EvalError("eval issue"); }', + expected: 'EvalError', + sample: 'try { throwEval(); } catch (e) { return e.constructor.name; }', + hints: ['Access constructor.name', 'Returns error class name'], + tags: ['error', 'eval-error', 'constructor'], + }, + { + id: 'js-error-061', + category: 'Error Handling', + difficulty: 'medium', + title: 'Async Error Handling', + text: 'Handle error in async function with try-catch.', + setup: 'async function asyncFail() { throw new Error("async error"); }', + setupCode: 'async function asyncFail() { throw new Error("async error"); }', + expected: 'async error', + sample: 'return asyncFail().catch(e => e.message);', + hints: ['Async throws become rejections', 'Use .catch() to handle'], + tags: ['error', 'async', 'promises'], + }, + { + id: 'js-error-062', + category: 'Control Flow', + difficulty: 'medium', + title: 'Nullish with Function Result', + text: 'Use nullish coalescing with function that may return null.', + setup: 'function maybeNull() { return null; }', + setupCode: 'function maybeNull() { return null; }', + expected: 'default', + sample: 'maybeNull() ?? "default"', + hints: ['Call function directly', 'Nullish provides default for null'], + tags: ['nullish-coalescing', 'functions', 'null'], + }, + { + id: 'js-error-063', + category: 'Error Handling', + difficulty: 'medium', + title: 'InternalError Simulation', + text: 'Create and throw a custom InternalError-like error.', + setup: 'class InternalError extends Error { constructor(msg) { super(msg); this.name = "InternalError"; } }', + setupCode: 'class InternalError extends Error { constructor(msg) { super(msg); this.name = "InternalError"; } }', + expected: 'InternalError: Stack overflow', + sample: 'try { throw new InternalError("Stack overflow"); } catch (e) { return `${e.name}: ${e.message}`; }', + hints: ['Throw custom error', 'Format name and message'], + tags: ['error', 'custom-error', 'formatting'], + }, + { + id: 'js-error-064', + category: 'Error Handling', + difficulty: 'medium', + title: 'Catch Without Variable', + text: 'Use catch without binding the error variable.', + setup: 'function mayFail() { throw new Error("fail"); }', + setupCode: 'function mayFail() { throw new Error("fail"); }', + expected: 'handled', + sample: 'try { mayFail(); } catch { return "handled"; }', + hints: ['Catch without (e)', 'Modern syntax allows this'], + tags: ['error', 'catch', 'syntax'], + }, + { + id: 'js-error-065', + category: 'Control Flow', + difficulty: 'medium', + title: 'Safe Array Destructure', + text: 'Safely destructure from potentially undefined source.', + setup: 'const data = undefined;', + setupCode: 'const data = undefined;', + expected: 'no-first', + sample: 'const [first] = data ?? []; return first ?? "no-first";', + hints: ['Provide empty array default', 'Then default for first element'], + tags: ['nullish-coalescing', 'destructuring', 'arrays'], + }, + { + id: 'js-error-066', + category: 'Error Handling', + difficulty: 'medium', + title: 'Promise.any with All Rejected', + text: 'Handle AggregateError when all promises reject.', + setup: 'const promises = [Promise.reject("e1"), Promise.reject("e2")];', + setupCode: 'const promises = [Promise.reject("e1"), Promise.reject("e2")];', + expected: 2, + sample: 'return Promise.any(promises).catch(e => e.errors.length);', + hints: ['Promise.any throws AggregateError', 'Access errors array'], + tags: ['error', 'promise-any', 'aggregate'], + }, + { + id: 'js-error-067', + category: 'Control Flow', + difficulty: 'medium', + title: 'Guard Clause Pattern', + text: 'Use early returns to handle error cases.', + setup: 'function validate(user) { if (!user) return { error: "No user" }; if (!user.email) return { error: "No email" }; return { valid: true }; }', + setupCode: 'function validate(user) { if (!user) return { error: "No user" }; if (!user.email) return { error: "No email" }; return { valid: true }; }', + expected: { error: 'No email' }, + sample: 'validate({ name: "Test" })', + hints: ['Guard clauses return early', 'Missing email triggers second guard'], + tags: ['guard-clause', 'validation', 'patterns'], + }, + { + id: 'js-error-068', + category: 'Error Handling', + difficulty: 'medium', + title: 'Dynamic Import Error', + text: 'Handle error from failing dynamic import.', + setup: 'const importModule = () => import("./nonexistent.js");', + setupCode: 'const importModule = () => import("./nonexistent.js");', + expected: true, + sample: 'return importModule().catch(e => e instanceof Error);', + hints: ['Dynamic import returns promise', 'Missing module rejects'], + tags: ['error', 'dynamic-import', 'modules'], + }, + { + id: 'js-error-069', + category: 'Control Flow', + difficulty: 'medium', + title: 'Switch with Default Error', + text: 'Throw error in switch default case.', + setup: 'function handleStatus(code) { switch(code) { case 200: return "OK"; case 404: return "Not Found"; default: throw new Error("Unknown: " + code); } }', + setupCode: 'function handleStatus(code) { switch(code) { case 200: return "OK"; case 404: return "Not Found"; default: throw new Error("Unknown: " + code); } }', + expected: 'Unknown: 500', + sample: 'try { handleStatus(500); } catch (e) { return e.message; }', + hints: ['Unknown code hits default', 'Throws with code in message'], + tags: ['error', 'switch', 'default'], + }, + { + id: 'js-error-070', + category: 'Error Handling', + difficulty: 'medium', + title: 'Error in Getter', + text: 'Handle error thrown by object getter.', + setup: 'const obj = { get value() { throw new Error("Getter failed"); } };', + setupCode: 'const obj = { get value() { throw new Error("Getter failed"); } };', + expected: 'Getter failed', + sample: 'try { obj.value; } catch (e) { return e.message; }', + hints: ['Accessing getter can throw', 'Wrap in try-catch'], + tags: ['error', 'getter', 'properties'], + }, + { + id: 'js-error-071', + category: 'Control Flow', + difficulty: 'medium', + title: 'Error in Setter', + text: 'Handle error thrown by object setter.', + setup: 'const obj = { set value(v) { if (v < 0) throw new Error("Negative not allowed"); } };', + setupCode: 'const obj = { set value(v) { if (v < 0) throw new Error("Negative not allowed"); } };', + expected: 'Negative not allowed', + sample: 'try { obj.value = -1; } catch (e) { return e.message; }', + hints: ['Setter validates input', 'Throws for negative'], + tags: ['error', 'setter', 'validation'], + }, + { + id: 'js-error-072', + category: 'Error Handling', + difficulty: 'medium', + title: 'Proxy Trap Error', + text: 'Handle error from Proxy get trap.', + setup: 'const handler = { get(t, p) { throw new Error("Access denied: " + p); } }; const proxy = new Proxy({}, handler);', + setupCode: 'const handler = { get(t, p) { throw new Error("Access denied: " + p); } }; const proxy = new Proxy({}, handler);', + expected: 'Access denied: secret', + sample: 'try { proxy.secret; } catch (e) { return e.message; }', + hints: ['Proxy trap intercepts access', 'Throws with property name'], + tags: ['error', 'proxy', 'traps'], + }, + { + id: 'js-error-073', + category: 'Error Handling', + difficulty: 'medium', + title: 'Symbol.iterator Error', + text: 'Handle error when iterating non-iterable.', + setup: 'const obj = { [Symbol.iterator]: () => ({ next: () => { throw new Error("Iterator error"); } }) };', + setupCode: 'const obj = { [Symbol.iterator]: () => ({ next: () => { throw new Error("Iterator error"); } }) };', + expected: 'Iterator error', + sample: 'try { [...obj]; } catch (e) { return e.message; }', + hints: ['Spread calls iterator', 'next() throws error'], + tags: ['error', 'iterator', 'spread'], + }, + { + id: 'js-error-074', + category: 'Control Flow', + difficulty: 'medium', + title: 'Generator Error Handling', + text: 'Handle error thrown inside generator.', + setup: 'function* gen() { yield 1; throw new Error("Generator error"); yield 2; }', + setupCode: 'function* gen() { yield 1; throw new Error("Generator error"); yield 2; }', + expected: { values: [1], error: 'Generator error' }, + sample: 'const values = []; const g = gen(); try { values.push(g.next().value); values.push(g.next().value); } catch (e) { return { values, error: e.message }; }', + hints: ['First next succeeds', 'Second next throws'], + tags: ['error', 'generator', 'iteration'], + }, + { + id: 'js-error-075', + category: 'Error Handling', + difficulty: 'medium', + title: 'Assertion Failed Message', + text: 'Catch assertion failure and return custom message.', + setup: 'function assertPositive(n) { if (n <= 0) throw new Error(`Expected positive, got ${n}`); return n; }', + setupCode: 'function assertPositive(n) { if (n <= 0) throw new Error(`Expected positive, got ${n}`); return n; }', + expected: 'Expected positive, got -5', + sample: 'try { assertPositive(-5); } catch (e) { return e.message; }', + hints: ['Negative triggers assertion', 'Message includes the value'], + tags: ['error', 'assertion', 'validation'], + }, + { + id: 'js-error-076', + category: 'Error Handling', + difficulty: 'hard', + title: 'Custom Error Hierarchy', + text: 'Create a hierarchy of errors and check inheritance.', + setup: 'class AppError extends Error { constructor(msg) { super(msg); this.name = "AppError"; } } class NetworkError extends AppError { constructor(msg) { super(msg); this.name = "NetworkError"; } }', + setupCode: 'class AppError extends Error { constructor(msg) { super(msg); this.name = "AppError"; } } class NetworkError extends AppError { constructor(msg) { super(msg); this.name = "NetworkError"; } }', + expected: [true, true, true], + sample: 'const e = new NetworkError("timeout"); return [e instanceof NetworkError, e instanceof AppError, e instanceof Error];', + hints: ['Check all levels of inheritance', 'instanceof checks prototype chain'], + tags: ['error', 'inheritance', 'custom-error'], + }, + { + id: 'js-error-077', + category: 'Error Handling', + difficulty: 'hard', + title: 'Error Factory Pattern', + text: 'Create a factory that produces typed errors.', + setup: 'const errorFactory = { validation: (msg) => { const e = new Error(msg); e.type = "VALIDATION"; return e; }, network: (msg) => { const e = new Error(msg); e.type = "NETWORK"; return e; } };', + setupCode: 'const errorFactory = { validation: (msg) => { const e = new Error(msg); e.type = "VALIDATION"; return e; }, network: (msg) => { const e = new Error(msg); e.type = "NETWORK"; return e; } };', + expected: 'VALIDATION', + sample: 'try { throw errorFactory.validation("Invalid email"); } catch (e) { return e.type; }', + hints: ['Use factory method', 'Access custom type property'], + tags: ['error', 'factory', 'patterns'], + }, + { + id: 'js-error-078', + category: 'Error Handling', + difficulty: 'hard', + title: 'Retry with Error Tracking', + text: 'Implement retry logic that tracks all errors.', + setup: 'let attempts = 0; function unreliable() { attempts++; if (attempts < 3) throw new Error(`Attempt ${attempts}`); return "success"; }', + setupCode: 'let attempts = 0; function unreliable() { attempts++; if (attempts < 3) throw new Error(`Attempt ${attempts}`); return "success"; }', + expected: { result: 'success', errors: ['Attempt 1', 'Attempt 2'] }, + sample: 'const errors = []; while (true) { try { return { result: unreliable(), errors }; } catch (e) { errors.push(e.message); } }', + hints: ['Loop until success', 'Collect errors in array'], + tags: ['error', 'retry', 'tracking'], + }, + { + id: 'js-error-079', + category: 'Error Handling', + difficulty: 'hard', + title: 'Error Serialization', + text: 'Serialize error to JSON-compatible object.', + setup: 'function createError() { const e = new TypeError("Invalid argument"); e.code = "ARG_001"; return e; }', + setupCode: 'function createError() { const e = new TypeError("Invalid argument"); e.code = "ARG_001"; return e; }', + expected: { name: 'TypeError', message: 'Invalid argument', code: 'ARG_001' }, + sample: 'const e = createError(); return { name: e.name, message: e.message, code: e.code };', + hints: ['Extract error properties', 'Build plain object'], + tags: ['error', 'serialization', 'json'], + }, + { + id: 'js-error-080', + category: 'Control Flow', + difficulty: 'hard', + title: 'Deep Optional Chain with Transform', + text: 'Navigate deep optional path and transform result.', + setup: 'const api = { response: { data: { users: [{ profile: { email: "test@example.com" } }] } } };', + setupCode: 'const api = { response: { data: { users: [{ profile: { email: "test@example.com" } }] } } };', + expected: 'TEST@EXAMPLE.COM', + sample: 'api?.response?.data?.users?.[0]?.profile?.email?.toUpperCase() ?? "NO EMAIL"', + hints: ['Chain all the way through', 'Call method at the end'], + tags: ['optional-chaining', 'deep-access', 'transform'], + }, + { + id: 'js-error-081', + category: 'Error Handling', + difficulty: 'hard', + title: 'Error Middleware Chain', + text: 'Pass error through middleware chain, each adding context.', + setup: 'const middlewares = [(e) => { e.context = []; e.context.push("m1"); return e; }, (e) => { e.context.push("m2"); return e; }];', + setupCode: 'const middlewares = [(e) => { e.context = []; e.context.push("m1"); return e; }, (e) => { e.context.push("m2"); return e; }];', + expected: ['m1', 'm2'], + sample: 'let err = new Error("test"); for (const m of middlewares) err = m(err); return err.context;', + hints: ['Chain middleware functions', 'Each modifies error'], + tags: ['error', 'middleware', 'patterns'], + }, + { + id: 'js-error-082', + category: 'Error Handling', + difficulty: 'hard', + title: 'Promise.allSettled Error Extraction', + text: 'Extract all error messages from settled promises.', + setup: 'const promises = [Promise.resolve(1), Promise.reject(new Error("fail1")), Promise.reject(new Error("fail2"))];', + setupCode: 'const promises = [Promise.resolve(1), Promise.reject(new Error("fail1")), Promise.reject(new Error("fail2"))];', + expected: ['fail1', 'fail2'], + sample: 'return Promise.allSettled(promises).then(results => results.filter(r => r.status === "rejected").map(r => r.reason.message));', + hints: ['Use Promise.allSettled', 'Filter rejected, map reasons'], + tags: ['error', 'promises', 'allSettled'], + }, + { + id: 'js-error-083', + category: 'Control Flow', + difficulty: 'hard', + title: 'Nullish with Computed Property', + text: 'Use nullish coalescing with computed property access.', + setup: 'const config = { settings: { theme: null } }; const key = "theme";', + setupCode: 'const config = { settings: { theme: null } }; const key = "theme";', + expected: 'dark', + sample: 'config.settings[key] ?? "dark"', + hints: ['Access with bracket notation', 'Nullish provides default'], + tags: ['nullish-coalescing', 'computed-property', 'dynamic'], + }, + { + id: 'js-error-084', + category: 'Error Handling', + difficulty: 'hard', + title: 'Error Recovery with State', + text: 'Recover from error while maintaining valid state.', + setup: 'let state = { count: 0, errors: [] }; function increment() { state.count++; if (state.count === 2) throw new Error("fail at 2"); }', + setupCode: 'let state = { count: 0, errors: [] }; function increment() { state.count++; if (state.count === 2) throw new Error("fail at 2"); }', + expected: { count: 3, errors: ['fail at 2'] }, + sample: 'for (let i = 0; i < 3; i++) { try { increment(); } catch (e) { state.errors.push(e.message); } } return state;', + hints: ['Continue after error', 'Track errors in state'], + tags: ['error', 'state', 'recovery'], + }, + { + id: 'js-error-085', + category: 'Error Handling', + difficulty: 'hard', + title: 'Async Error Aggregation', + text: 'Aggregate errors from multiple async operations.', + setup: 'const ops = [() => Promise.reject(new Error("op1")), () => Promise.resolve("ok"), () => Promise.reject(new Error("op3"))];', + setupCode: 'const ops = [() => Promise.reject(new Error("op1")), () => Promise.resolve("ok"), () => Promise.reject(new Error("op3"))];', + expected: ['op1', 'op3'], + sample: 'return Promise.all(ops.map(op => op().catch(e => ({ error: e.message })))).then(results => results.filter(r => r.error).map(r => r.error));', + hints: ['Catch each operation individually', 'Mark errors in results'], + tags: ['error', 'async', 'aggregation'], + }, + { + id: 'js-error-086', + category: 'Error Handling', + difficulty: 'hard', + title: 'Transactional Error Handling', + text: 'Rollback changes on error.', + setup: 'let data = [1, 2, 3]; function transaction(fn) { const backup = [...data]; try { fn(); } catch (e) { data = backup; throw e; } }', + setupCode: 'let data = [1, 2, 3]; function transaction(fn) { const backup = [...data]; try { fn(); } catch (e) { data = backup; throw e; } }', + expected: [1, 2, 3], + sample: 'try { transaction(() => { data.push(4); throw new Error("abort"); }); } catch {} return data;', + hints: ['Transaction restores on error', 'Data is rolled back'], + tags: ['error', 'transaction', 'rollback'], + }, + { + id: 'js-error-087', + category: 'Control Flow', + difficulty: 'hard', + title: 'Complex Default Chain', + text: 'Chain multiple defaults with different operators.', + setup: 'const a = undefined; const b = null; const c = ""; const d = "value";', + setupCode: 'const a = undefined; const b = null; const c = ""; const d = "value";', + expected: '', + sample: 'a ?? b ?? c ?? d', + hints: ['?? stops at empty string', 'Empty string is not nullish'], + tags: ['nullish-coalescing', 'chaining', 'falsy'], + }, + { + id: 'js-error-088', + category: 'Error Handling', + difficulty: 'hard', + title: 'Error Context Builder', + text: 'Build error with rich context information.', + setup: 'class ContextError extends Error { constructor(msg, context = {}) { super(msg); this.context = context; this.name = "ContextError"; } addContext(key, value) { this.context[key] = value; return this; } }', + setupCode: 'class ContextError extends Error { constructor(msg, context = {}) { super(msg); this.context = context; this.name = "ContextError"; } addContext(key, value) { this.context[key] = value; return this; } }', + expected: { userId: 123, action: 'save' }, + sample: 'const e = new ContextError("Failed").addContext("userId", 123).addContext("action", "save"); return e.context;', + hints: ['Chain addContext calls', 'Return the context object'], + tags: ['error', 'context', 'builder'], + }, + { + id: 'js-error-089', + category: 'Error Handling', + difficulty: 'hard', + title: 'Deferred Error Handling', + text: 'Collect operations and handle all errors at once.', + setup: 'const operations = [() => 1, () => { throw new Error("fail"); }, () => 3];', + setupCode: 'const operations = [() => 1, () => { throw new Error("fail"); }, () => 3];', + expected: { results: [1, 3], errors: ['fail'] }, + sample: 'const results = [], errors = []; operations.forEach(op => { try { results.push(op()); } catch (e) { errors.push(e.message); } }); return { results, errors };', + hints: ['Try each operation', 'Separate successes from failures'], + tags: ['error', 'deferred', 'batch'], + }, + { + id: 'js-error-090', + category: 'Control Flow', + difficulty: 'hard', + title: 'Optional Chaining with Side Effects', + text: 'Conditionally call function only if object exists.', + setup: 'let called = false; const obj = { notify: () => { called = true; } };', + setupCode: 'let called = false; const obj = { notify: () => { called = true; } };', + expected: true, + sample: 'obj?.notify?.(); return called;', + hints: ['Use ?.() for safe call', 'Check if called was set'], + tags: ['optional-chaining', 'side-effects', 'functions'], + }, + { + id: 'js-error-091', + category: 'Error Handling', + difficulty: 'hard', + title: 'Error Rate Limiter', + text: 'Track error count and throw if too many errors.', + setup: 'const errorTracker = { count: 0, limit: 3, track() { this.count++; if (this.count > this.limit) throw new Error("Too many errors"); } };', + setupCode: 'const errorTracker = { count: 0, limit: 3, track() { this.count++; if (this.count > this.limit) throw new Error("Too many errors"); } };', + expected: 'Too many errors', + sample: 'try { for (let i = 0; i < 5; i++) errorTracker.track(); } catch (e) { return e.message; }', + hints: ['Track exceeds limit', 'Throws on 4th error'], + tags: ['error', 'rate-limiting', 'tracking'], + }, + { + id: 'js-error-092', + category: 'Error Handling', + difficulty: 'hard', + title: 'Async Finally Cleanup', + text: 'Ensure cleanup runs even with async errors.', + setup: 'let cleaned = false; async function work() { throw new Error("async fail"); }', + setupCode: 'let cleaned = false; async function work() { throw new Error("async fail"); }', + expected: true, + sample: 'return work().catch(() => {}).finally(() => { cleaned = true; }).then(() => cleaned);', + hints: ['finally runs after catch', 'Set cleaned in finally'], + tags: ['error', 'async', 'finally'], + }, + { + id: 'js-error-093', + category: 'Control Flow', + difficulty: 'hard', + title: 'Safe Map Operation', + text: 'Map over array, replacing errors with null.', + setup: 'const items = [1, 2, 3]; function process(x) { if (x === 2) throw new Error("bad"); return x * 10; }', + setupCode: 'const items = [1, 2, 3]; function process(x) { if (x === 2) throw new Error("bad"); return x * 10; }', + expected: [10, null, 30], + sample: 'items.map(x => { try { return process(x); } catch { return null; } })', + hints: ['Wrap each map call in try', 'Return null on error'], + tags: ['error', 'map', 'defensive'], + }, + { + id: 'js-error-094', + category: 'Error Handling', + difficulty: 'hard', + title: 'Error Retry with Backoff', + text: 'Simulate retry with increasing delay tracking.', + setup: 'let delays = []; let attempt = 0; function flaky() { attempt++; delays.push(attempt * 100); if (attempt < 3) throw new Error("retry"); return "done"; }', + setupCode: 'let delays = []; let attempt = 0; function flaky() { attempt++; delays.push(attempt * 100); if (attempt < 3) throw new Error("retry"); return "done"; }', + expected: { result: 'done', delays: [100, 200, 300] }, + sample: 'let result; while (!result) { try { result = flaky(); } catch {} } return { result, delays };', + hints: ['Loop until success', 'delays tracks attempts'], + tags: ['error', 'retry', 'backoff'], + }, + { + id: 'js-error-095', + category: 'Error Handling', + difficulty: 'hard', + title: 'Cross-Cutting Error Handler', + text: 'Wrap function to catch and transform all errors.', + setup: 'function withErrorHandler(fn, handler) { return (...args) => { try { return fn(...args); } catch (e) { return handler(e); } }; }', + setupCode: 'function withErrorHandler(fn, handler) { return (...args) => { try { return fn(...args); } catch (e) { return handler(e); } }; }', + expected: 'Error: test error', + sample: 'const risky = () => { throw new Error("test error"); }; const safe = withErrorHandler(risky, e => "Error: " + e.message); return safe();', + hints: ['Use wrapper function', 'Handler transforms error'], + tags: ['error', 'wrapper', 'higher-order'], + }, + { + id: 'js-error-096', + category: 'Control Flow', + difficulty: 'hard', + title: 'Optional Chaining Assignment', + text: 'Check if assignment target exists before assigning.', + setup: 'const obj = { data: { items: [] } };', + setupCode: 'const obj = { data: { items: [] } };', + expected: { data: { items: [1] } }, + sample: 'obj.data?.items?.push(1); return obj;', + hints: ['Use ?. before method call', 'push modifies array'], + tags: ['optional-chaining', 'mutation', 'arrays'], + }, + { + id: 'js-error-097', + category: 'Error Handling', + difficulty: 'hard', + title: 'Error Event Emitter Pattern', + text: 'Emit error events and collect them.', + setup: 'const emitter = { handlers: [], on(h) { this.handlers.push(h); }, emit(e) { this.handlers.forEach(h => h(e)); } };', + setupCode: 'const emitter = { handlers: [], on(h) { this.handlers.push(h); }, emit(e) { this.handlers.forEach(h => h(e)); } };', + expected: ['Error: e1', 'Error: e2'], + sample: 'const errors = []; emitter.on(e => errors.push("Error: " + e)); emitter.emit("e1"); emitter.emit("e2"); return errors;', + hints: ['Register handler first', 'Emit multiple errors'], + tags: ['error', 'events', 'patterns'], + }, + { + id: 'js-error-098', + category: 'Error Handling', + difficulty: 'hard', + title: 'Validation Schema Error', + text: 'Validate object against schema, collect all errors.', + setup: 'const schema = { name: v => v ? null : "Name required", age: v => v > 0 ? null : "Age must be positive" };', + setupCode: 'const schema = { name: v => v ? null : "Name required", age: v => v > 0 ? null : "Age must be positive" };', + expected: ['Name required', 'Age must be positive'], + sample: 'const obj = { name: "", age: -5 }; return Object.entries(schema).map(([k, v]) => v(obj[k])).filter(Boolean);', + hints: ['Run each validator', 'Filter out nulls'], + tags: ['error', 'validation', 'schema'], + }, + { + id: 'js-error-099', + category: 'Control Flow', + difficulty: 'hard', + title: 'Defensive Object Access', + text: 'Safely access nested object with multiple fallbacks.', + setup: 'const response = { status: 200 };', + setupCode: 'const response = { status: 200 };', + expected: 'Unknown', + sample: 'response?.data?.user?.name ?? response?.error?.message ?? "Unknown"', + hints: ['Chain multiple optional paths', 'Use ?? for final default'], + tags: ['optional-chaining', 'fallback', 'defensive'], + }, + { + id: 'js-error-100', + category: 'Error Handling', + difficulty: 'hard', + title: 'Circuit Breaker Pattern', + text: 'Implement simple circuit breaker that opens after failures.', + setup: 'const breaker = { failures: 0, open: false, call(fn) { if (this.open) throw new Error("Circuit open"); try { return fn(); } catch (e) { this.failures++; if (this.failures >= 2) this.open = true; throw e; } } };', + setupCode: 'const breaker = { failures: 0, open: false, call(fn) { if (this.open) throw new Error("Circuit open"); try { return fn(); } catch (e) { this.failures++; if (this.failures >= 2) this.open = true; throw e; } } };', + expected: 'Circuit open', + sample: 'const fail = () => { throw new Error("fail"); }; try { breaker.call(fail); } catch {} try { breaker.call(fail); } catch {} try { breaker.call(fail); } catch (e) { return e.message; }', + hints: ['First two failures count', 'Third call circuit is open'], + tags: ['error', 'circuit-breaker', 'patterns'], + }, + { + id: 'js-error-101', + category: 'Error Handling', + difficulty: 'hard', + title: 'Error Boundary Simulation', + text: 'Create an error boundary that catches and reports errors.', + setup: 'const boundary = { errors: [], wrap(fn) { return (...args) => { try { return { success: true, data: fn(...args) }; } catch (e) { this.errors.push(e.message); return { success: false, error: e.message }; } }; } };', + setupCode: 'const boundary = { errors: [], wrap(fn) { return (...args) => { try { return { success: true, data: fn(...args) }; } catch (e) { this.errors.push(e.message); return { success: false, error: e.message }; } }; } };', + expected: { result: { success: false, error: 'boom' }, tracked: ['boom'] }, + sample: 'const risky = () => { throw new Error("boom"); }; const safe = boundary.wrap(risky); const result = safe(); return { result, tracked: boundary.errors };', + hints: ['Use wrapped function', 'Check both result and tracked'], + tags: ['error', 'boundary', 'patterns'], + }, + { + id: 'js-error-102', + category: 'Control Flow', + difficulty: 'hard', + title: 'Type Coercion Guard', + text: 'Guard against type coercion issues with strict checks.', + setup: 'function strictEquals(a, b) { if (typeof a !== typeof b) throw new TypeError("Type mismatch"); return a === b; }', + setupCode: 'function strictEquals(a, b) { if (typeof a !== typeof b) throw new TypeError("Type mismatch"); return a === b; }', + expected: 'Type mismatch', + sample: 'try { strictEquals(1, "1"); } catch (e) { return e.message; }', + hints: ['Different types throw', 'Number vs string'], + tags: ['error', 'type-guard', 'strict'], + }, + { + id: 'js-error-103', + category: 'Error Handling', + difficulty: 'hard', + title: 'Async Error Chain', + text: 'Chain async operations, each handling previous errors.', + setup: 'async function step1() { throw new Error("step1 failed"); } async function step2() { return "step2 ok"; }', + setupCode: 'async function step1() { throw new Error("step1 failed"); } async function step2() { return "step2 ok"; }', + expected: 'Recovered: step2 ok', + sample: 'return step1().catch(() => step2()).then(r => "Recovered: " + r);', + hints: ['Catch first, chain second', 'Recover in catch'], + tags: ['error', 'async', 'chaining'], + }, + { + id: 'js-error-104', + category: 'Error Handling', + difficulty: 'hard', + title: 'Error Sanitization', + text: 'Remove sensitive information from error before logging.', + setup: 'function sanitizeError(e) { const safe = new Error(e.message.replace(/password=\\w+/, "password=***")); safe.name = e.name; return safe; }', + setupCode: 'function sanitizeError(e) { const safe = new Error(e.message.replace(/password=\\w+/, "password=***")); safe.name = e.name; return safe; }', + expected: 'Failed with password=***', + sample: 'const original = new Error("Failed with password=secret123"); return sanitizeError(original).message;', + hints: ['Password is redacted', 'Message is sanitized'], + tags: ['error', 'security', 'sanitization'], + }, + { + id: 'js-error-105', + category: 'Control Flow', + difficulty: 'hard', + title: 'Safe Reduce Operation', + text: 'Reduce array, skipping elements that throw.', + setup: 'const items = [1, 2, 3, 4]; function addSafe(acc, x) { if (x === 3) throw new Error("skip"); return acc + x; }', + setupCode: 'const items = [1, 2, 3, 4]; function addSafe(acc, x) { if (x === 3) throw new Error("skip"); return acc + x; }', + expected: 7, + sample: 'items.reduce((acc, x) => { try { return addSafe(acc, x); } catch { return acc; } }, 0)', + hints: ['Wrap reduce callback', 'Return accumulator on error'], + tags: ['error', 'reduce', 'defensive'], + }, + { + id: 'js-error-106', + category: 'Error Handling', + difficulty: 'hard', + title: 'Promise Race Error', + text: 'Handle first rejection in Promise.race.', + setup: 'const slow = new Promise(r => setTimeout(() => r("slow"), 100)); const fast = Promise.reject(new Error("fast error"));', + setupCode: 'const slow = new Promise(r => setTimeout(() => r("slow"), 100)); const fast = Promise.reject(new Error("fast error"));', + expected: 'fast error', + sample: 'return Promise.race([slow, fast]).catch(e => e.message);', + hints: ['Rejection wins the race', 'Catch handles rejection'], + tags: ['error', 'promise-race', 'async'], + }, + { + id: 'js-error-107', + category: 'Error Handling', + difficulty: 'hard', + title: 'WeakRef Error Pattern', + text: 'Handle case when WeakRef target is garbage collected.', + setup: 'function getSafe(ref) { const obj = ref.deref(); if (!obj) throw new Error("Object collected"); return obj; }', + setupCode: 'function getSafe(ref) { const obj = ref.deref(); if (!obj) throw new Error("Object collected"); return obj; }', + expected: { name: 'test' }, + sample: 'const obj = { name: "test" }; const ref = new WeakRef(obj); return getSafe(ref);', + hints: ['Object still exists', 'deref returns object'], + tags: ['error', 'weakref', 'memory'], + }, + { + id: 'js-error-108', + category: 'Control Flow', + difficulty: 'hard', + title: 'Abort Controller Error', + text: 'Use AbortController to simulate cancellation error.', + setup: 'const controller = new AbortController();', + setupCode: 'const controller = new AbortController();', + expected: 'AbortError', + sample: 'controller.abort(); try { if (controller.signal.aborted) throw new DOMException("Aborted", "AbortError"); } catch (e) { return e.name; }', + hints: ['Check signal.aborted', 'Throw DOMException'], + tags: ['error', 'abort', 'cancellation'], + }, + { + id: 'js-error-109', + category: 'Error Handling', + difficulty: 'hard', + title: 'Error Stack Manipulation', + text: 'Capture and modify error stack trace.', + setup: 'function captureStack() { const e = new Error("test"); return e.stack.split("\\n").length > 1; }', + setupCode: 'function captureStack() { const e = new Error("test"); return e.stack.split("\\n").length > 1; }', + expected: true, + sample: 'captureStack()', + hints: ['Stack has multiple lines', 'Each line is a frame'], + tags: ['error', 'stack', 'debugging'], + }, + { + id: 'js-error-110', + category: 'Error Handling', + difficulty: 'hard', + title: 'Async Generator Error', + text: 'Handle error in async generator.', + setup: 'async function* asyncGen() { yield 1; throw new Error("async gen error"); }', + setupCode: 'async function* asyncGen() { yield 1; throw new Error("async gen error"); }', + expected: 'async gen error', + sample: 'const gen = asyncGen(); return gen.next().then(() => gen.next()).catch(e => e.message);', + hints: ['First next resolves', 'Second next rejects'], + tags: ['error', 'async-generator', 'iteration'], + }, + // ======================================== + // FUNCTIONAL PROGRAMMING + // ======================================== + { + id: 'js-functional-001', + category: 'Functional Programming', + difficulty: 'easy', + title: 'Pure Function - Double', + text: 'Create a pure function that doubles a number without side effects.', + setup: 'const num = 5;', + setupCode: 'const num = 5;', + expected: 10, + sample: 'const double = x => x * 2; double(num)', + hints: ['Pure functions always return the same output for same input', 'No external state modification'], + tags: ['functional', 'pure-function', 'basics'], + }, + { + id: 'js-functional-002', + category: 'Functional Programming', + difficulty: 'easy', + title: 'Immutable Array Update', + text: 'Add an element to the array without mutating the original.', + setup: 'const arr = [1, 2, 3];', + setupCode: 'const arr = [1, 2, 3];', + expected: [1, 2, 3, 4], + sample: '[...arr, 4]', + hints: ['Use spread operator', 'Create a new array'], + tags: ['functional', 'immutability', 'spread'], + }, + { + id: 'js-functional-003', + category: 'Higher-Order Functions', + difficulty: 'easy', + title: 'Simple Map Transformation', + text: 'Use map to square each number in the array.', + setup: 'const nums = [1, 2, 3, 4];', + setupCode: 'const nums = [1, 2, 3, 4];', + expected: [1, 4, 9, 16], + sample: 'nums.map(x => x * x)', + hints: ['map transforms each element', 'Return the squared value'], + tags: ['functional', 'map', 'higher-order'], + }, + { + id: 'js-functional-004', + category: 'Higher-Order Functions', + difficulty: 'easy', + title: 'Filter with Predicate', + text: 'Filter numbers greater than 5 using a predicate function.', + setup: 'const nums = [2, 7, 3, 9, 1, 8];', + setupCode: 'const nums = [2, 7, 3, 9, 1, 8];', + expected: [7, 9, 8], + sample: 'const greaterThan5 = x => x > 5; nums.filter(greaterThan5)', + hints: ['Define the predicate separately', 'Pass function reference to filter'], + tags: ['functional', 'filter', 'predicate'], + }, + { + id: 'js-functional-005', + category: 'Higher-Order Functions', + difficulty: 'easy', + title: 'Reduce to Sum', + text: 'Use reduce to calculate the sum of all numbers.', + setup: 'const nums = [1, 2, 3, 4, 5];', + setupCode: 'const nums = [1, 2, 3, 4, 5];', + expected: 15, + sample: 'nums.reduce((acc, x) => acc + x, 0)', + hints: ['reduce accumulates a single value', 'Start with initial value 0'], + tags: ['functional', 'reduce', 'accumulator'], + }, + { + id: 'js-functional-006', + category: 'Functional Programming', + difficulty: 'easy', + title: 'Identity Function', + text: 'Create an identity function that returns its input unchanged.', + setup: 'const value = "hello";', + setupCode: 'const value = "hello";', + expected: 'hello', + sample: 'const identity = x => x; identity(value)', + hints: ['Return exactly what you receive', 'Simplest pure function'], + tags: ['functional', 'identity', 'basics'], + }, + { + id: 'js-functional-007', + category: 'Functional Programming', + difficulty: 'easy', + title: 'Constant Function', + text: 'Create a function that always returns the same value regardless of input.', + setup: 'const inputs = [1, 2, 3];', + setupCode: 'const inputs = [1, 2, 3];', + expected: [42, 42, 42], + sample: 'const always = x => () => x; inputs.map(always(42))', + hints: ['Return a function that ignores its argument', 'Closure captures the constant'], + tags: ['functional', 'constant', 'closure'], + }, + { + id: 'js-functional-008', + category: 'Closures', + difficulty: 'easy', + title: 'Simple Counter Closure', + text: 'Create a counter using closures that increments each time called.', + setup: 'let result = [];', + setupCode: 'let result = [];', + expected: [1, 2, 3], + sample: 'const makeCounter = () => { let count = 0; return () => ++count; }; const counter = makeCounter(); result = [counter(), counter(), counter()]', + hints: ['Inner function captures outer variable', 'Each call modifies the closed-over variable'], + tags: ['functional', 'closure', 'counter'], + }, + { + id: 'js-functional-009', + category: 'Functional Programming', + difficulty: 'easy', + title: 'Negate Function', + text: 'Create a function that negates a boolean predicate.', + setup: 'const isEven = x => x % 2 === 0; const nums = [1, 2, 3, 4];', + setupCode: 'const isEven = x => x % 2 === 0; const nums = [1, 2, 3, 4];', + expected: [1, 3], + sample: 'const negate = fn => x => !fn(x); nums.filter(negate(isEven))', + hints: ['Return a new function that negates result', 'Apply the original function and negate'], + tags: ['functional', 'negate', 'predicate'], + }, + { + id: 'js-functional-010', + category: 'Functional Programming', + difficulty: 'easy', + title: 'Immutable Object Update', + text: 'Update the age property without mutating the original object.', + setup: 'const person = { name: "Alice", age: 25 };', + setupCode: 'const person = { name: "Alice", age: 25 };', + expected: { name: 'Alice', age: 26 }, + sample: '({ ...person, age: person.age + 1 })', + hints: ['Spread the original object', 'Override the specific property'], + tags: ['functional', 'immutability', 'object'], + }, + { + id: 'js-functional-011', + category: 'Higher-Order Functions', + difficulty: 'easy', + title: 'Every Check', + text: 'Check if every number in the array is positive.', + setup: 'const nums = [1, 2, 3, 4, 5];', + setupCode: 'const nums = [1, 2, 3, 4, 5];', + expected: true, + sample: 'nums.every(x => x > 0)', + hints: ['every returns true if all elements pass', 'Short-circuits on first false'], + tags: ['functional', 'every', 'predicate'], + }, + { + id: 'js-functional-012', + category: 'Higher-Order Functions', + difficulty: 'easy', + title: 'Some Check', + text: 'Check if at least one number is greater than 10.', + setup: 'const nums = [3, 7, 12, 5];', + setupCode: 'const nums = [3, 7, 12, 5];', + expected: true, + sample: 'nums.some(x => x > 10)', + hints: ['some returns true if any element passes', 'Short-circuits on first true'], + tags: ['functional', 'some', 'predicate'], + }, + { + id: 'js-functional-013', + category: 'Functional Programming', + difficulty: 'easy', + title: 'Apply Function to Array', + text: 'Apply Math.max to an array using spread.', + setup: 'const nums = [3, 7, 2, 9, 4];', + setupCode: 'const nums = [3, 7, 2, 9, 4];', + expected: 9, + sample: 'Math.max(...nums)', + hints: ['Spread converts array to arguments', 'Math.max takes multiple arguments'], + tags: ['functional', 'apply', 'spread'], + }, + { + id: 'js-functional-014', + category: 'Functional Programming', + difficulty: 'easy', + title: 'First Element Extractor', + text: 'Create a function that extracts the first element of an array.', + setup: 'const arr = [5, 10, 15];', + setupCode: 'const arr = [5, 10, 15];', + expected: 5, + sample: 'const head = ([first]) => first; head(arr)', + hints: ['Use destructuring in parameter', 'Return just the first element'], + tags: ['functional', 'destructuring', 'head'], + }, + { + id: 'js-functional-015', + category: 'Functional Programming', + difficulty: 'easy', + title: 'Tail of Array', + text: 'Create a function that returns all elements except the first.', + setup: 'const arr = [1, 2, 3, 4];', + setupCode: 'const arr = [1, 2, 3, 4];', + expected: [2, 3, 4], + sample: 'const tail = ([, ...rest]) => rest; tail(arr)', + hints: ['Use rest parameter in destructuring', 'Skip the first element'], + tags: ['functional', 'destructuring', 'tail'], + }, + { + id: 'js-functional-016', + category: 'Higher-Order Functions', + difficulty: 'easy', + title: 'Find First Match', + text: 'Find the first number greater than 5.', + setup: 'const nums = [2, 4, 6, 8, 10];', + setupCode: 'const nums = [2, 4, 6, 8, 10];', + expected: 6, + sample: 'nums.find(x => x > 5)', + hints: ['find returns first matching element', 'Returns undefined if none found'], + tags: ['functional', 'find', 'search'], + }, + { + id: 'js-functional-017', + category: 'Functional Programming', + difficulty: 'easy', + title: 'Flatten One Level', + text: 'Flatten a nested array by one level.', + setup: 'const nested = [[1, 2], [3, 4], [5]];', + setupCode: 'const nested = [[1, 2], [3, 4], [5]];', + expected: [1, 2, 3, 4, 5], + sample: 'nested.flat()', + hints: ['flat() flattens one level by default', 'Can also use flatMap or reduce'], + tags: ['functional', 'flat', 'array'], + }, + { + id: 'js-functional-018', + category: 'Higher-Order Functions', + difficulty: 'easy', + title: 'FlatMap Basic', + text: 'Use flatMap to duplicate each number.', + setup: 'const nums = [1, 2, 3];', + setupCode: 'const nums = [1, 2, 3];', + expected: [1, 1, 2, 2, 3, 3], + sample: 'nums.flatMap(x => [x, x])', + hints: ['flatMap maps then flattens', 'Return an array from the callback'], + tags: ['functional', 'flatMap', 'array'], + }, + { + id: 'js-functional-019', + category: 'Functional Programming', + difficulty: 'easy', + title: 'Last Element', + text: 'Get the last element of an array functionally.', + setup: 'const arr = [1, 2, 3, 4, 5];', + setupCode: 'const arr = [1, 2, 3, 4, 5];', + expected: 5, + sample: 'arr.at(-1)', + hints: ['at() accepts negative indices', 'Or use slice(-1)[0]'], + tags: ['functional', 'at', 'array'], + }, + { + id: 'js-functional-020', + category: 'Functional Programming', + difficulty: 'easy', + title: 'Reverse Immutably', + text: 'Reverse an array without mutating the original.', + setup: 'const arr = [1, 2, 3];', + setupCode: 'const arr = [1, 2, 3];', + expected: [3, 2, 1], + sample: '[...arr].reverse()', + hints: ['Copy first with spread', 'Then apply reverse'], + tags: ['functional', 'immutability', 'reverse'], + }, + { + id: 'js-functional-021', + category: 'Closures', + difficulty: 'medium', + title: 'Private State with Closure', + text: 'Create a bank account with private balance using closure.', + setup: 'let result;', + setupCode: 'let result;', + expected: { balance: 150, canWithdraw: true }, + sample: 'const createAccount = (initial) => { let balance = initial; return { deposit: amt => balance += amt, withdraw: amt => balance -= amt, getBalance: () => balance }; }; const acc = createAccount(100); acc.deposit(100); acc.withdraw(50); result = { balance: acc.getBalance(), canWithdraw: acc.getBalance() > 0 }', + hints: ['Closure hides the balance variable', 'Return object with methods to access it'], + tags: ['functional', 'closure', 'encapsulation'], + }, + { + id: 'js-functional-022', + category: 'Higher-Order Functions', + difficulty: 'medium', + title: 'Custom Map Implementation', + text: 'Implement your own map function using reduce.', + setup: 'const nums = [1, 2, 3];', + setupCode: 'const nums = [1, 2, 3];', + expected: [2, 4, 6], + sample: 'const myMap = (arr, fn) => arr.reduce((acc, x) => [...acc, fn(x)], []); myMap(nums, x => x * 2)', + hints: ['reduce can build any data structure', 'Accumulate transformed elements'], + tags: ['functional', 'map', 'reduce', 'implementation'], + }, + { + id: 'js-functional-023', + category: 'Higher-Order Functions', + difficulty: 'medium', + title: 'Custom Filter Implementation', + text: 'Implement your own filter function using reduce.', + setup: 'const nums = [1, 2, 3, 4, 5, 6];', + setupCode: 'const nums = [1, 2, 3, 4, 5, 6];', + expected: [2, 4, 6], + sample: 'const myFilter = (arr, pred) => arr.reduce((acc, x) => pred(x) ? [...acc, x] : acc, []); myFilter(nums, x => x % 2 === 0)', + hints: ['Only add element if predicate is true', 'Use ternary in reduce'], + tags: ['functional', 'filter', 'reduce', 'implementation'], + }, + { + id: 'js-functional-024', + category: 'Functional Programming', + difficulty: 'medium', + title: 'Simple Compose', + text: 'Compose two functions: add 1, then multiply by 2.', + setup: 'const add1 = x => x + 1; const mult2 = x => x * 2; const num = 5;', + setupCode: 'const add1 = x => x + 1; const mult2 = x => x * 2; const num = 5;', + expected: 12, + sample: 'const compose = (f, g) => x => f(g(x)); compose(mult2, add1)(num)', + hints: ['compose applies right to left', 'g runs first, then f'], + tags: ['functional', 'compose', 'composition'], + }, + { + id: 'js-functional-025', + category: 'Functional Programming', + difficulty: 'medium', + title: 'Simple Pipe', + text: 'Pipe two functions: multiply by 2, then add 1.', + setup: 'const add1 = x => x + 1; const mult2 = x => x * 2; const num = 5;', + setupCode: 'const add1 = x => x + 1; const mult2 = x => x * 2; const num = 5;', + expected: 11, + sample: 'const pipe = (f, g) => x => g(f(x)); pipe(mult2, add1)(num)', + hints: ['pipe applies left to right', 'f runs first, then g'], + tags: ['functional', 'pipe', 'composition'], + }, + { + id: 'js-functional-026', + category: 'Functional Programming', + difficulty: 'medium', + title: 'Curried Add', + text: 'Create a curried add function.', + setup: 'const result = [];', + setupCode: 'const result = [];', + expected: [5, 7, 10], + sample: 'const add = a => b => a + b; const add3 = add(3); result.push(add3(2), add3(4), add(5)(5))', + hints: ['Return a function that takes second arg', 'Partial application creates specialized functions'], + tags: ['functional', 'currying', 'partial-application'], + }, + { + id: 'js-functional-027', + category: 'Functional Programming', + difficulty: 'medium', + title: 'Curried Multiply', + text: 'Create a curried multiply function and use it with map.', + setup: 'const nums = [1, 2, 3, 4];', + setupCode: 'const nums = [1, 2, 3, 4];', + expected: [3, 6, 9, 12], + sample: 'const multiply = a => b => a * b; nums.map(multiply(3))', + hints: ['multiply(3) returns a function', 'That function multiplies its arg by 3'], + tags: ['functional', 'currying', 'map'], + }, + { + id: 'js-functional-028', + category: 'Closures', + difficulty: 'medium', + title: 'Function Factory', + text: 'Create a function that generates greeting functions.', + setup: 'let greetings;', + setupCode: 'let greetings;', + expected: ['Hello, World!', 'Hi, World!', 'Hello, Alice!'], + sample: 'const makeGreeter = greeting => name => `${greeting}, ${name}!`; const sayHello = makeGreeter("Hello"); const sayHi = makeGreeter("Hi"); greetings = [sayHello("World"), sayHi("World"), sayHello("Alice")]', + hints: ['Outer function captures greeting', 'Inner function uses both variables'], + tags: ['functional', 'closure', 'factory'], + }, + { + id: 'js-functional-029', + category: 'Functional Programming', + difficulty: 'medium', + title: 'Partial Application', + text: 'Implement a partial application helper.', + setup: 'const add3 = (a, b, c) => a + b + c;', + setupCode: 'const add3 = (a, b, c) => a + b + c;', + expected: 15, + sample: 'const partial = (fn, ...args) => (...more) => fn(...args, ...more); partial(add3, 5, 5)(5)', + hints: ['Capture initial arguments', 'Combine with later arguments'], + tags: ['functional', 'partial-application', 'spread'], + }, + { + id: 'js-functional-030', + category: 'Higher-Order Functions', + difficulty: 'medium', + title: 'Reduce to Object', + text: 'Convert an array of pairs to an object using reduce.', + setup: 'const pairs = [["a", 1], ["b", 2], ["c", 3]];', + setupCode: 'const pairs = [["a", 1], ["b", 2], ["c", 3]];', + expected: { a: 1, b: 2, c: 3 }, + sample: 'pairs.reduce((obj, [k, v]) => ({ ...obj, [k]: v }), {})', + hints: ['Destructure each pair', 'Spread accumulator and add new key'], + tags: ['functional', 'reduce', 'object'], + }, + { + id: 'js-functional-031', + category: 'Functional Programming', + difficulty: 'medium', + title: 'Memoize Simple', + text: 'Create a simple memoization function for single-argument functions.', + setup: 'let callCount = 0; const expensive = x => { callCount++; return x * 2; };', + setupCode: 'let callCount = 0; const expensive = x => { callCount++; return x * 2; };', + expected: { results: [10, 10, 20], calls: 2 }, + sample: 'const memoize = fn => { const cache = {}; return x => x in cache ? cache[x] : cache[x] = fn(x); }; const memoized = memoize(expensive); const results = [memoized(5), memoized(5), memoized(10)]; ({ results, calls: callCount })', + hints: ['Store results in a cache object', 'Check cache before calling function'], + tags: ['functional', 'memoization', 'cache'], + }, + { + id: 'js-functional-032', + category: 'Higher-Order Functions', + difficulty: 'medium', + title: 'Group By', + text: 'Group array elements by the result of a function.', + setup: 'const nums = [1, 2, 3, 4, 5, 6];', + setupCode: 'const nums = [1, 2, 3, 4, 5, 6];', + expected: { odd: [1, 3, 5], even: [2, 4, 6] }, + sample: 'nums.reduce((groups, n) => { const key = n % 2 === 0 ? "even" : "odd"; groups[key] = [...(groups[key] || []), n]; return groups; }, {})', + hints: ['Determine group key for each element', 'Initialize group array if needed'], + tags: ['functional', 'reduce', 'groupBy'], + }, + { + id: 'js-functional-033', + category: 'Functional Programming', + difficulty: 'medium', + title: 'Flip Arguments', + text: 'Create a function that flips the first two arguments of a function.', + setup: 'const divide = (a, b) => a / b;', + setupCode: 'const divide = (a, b) => a / b;', + expected: 2, + sample: 'const flip = fn => (a, b, ...rest) => fn(b, a, ...rest); flip(divide)(5, 10)', + hints: ['Swap first two parameters', 'Pass rest unchanged'], + tags: ['functional', 'flip', 'higher-order'], + }, + { + id: 'js-functional-034', + category: 'Functional Programming', + difficulty: 'medium', + title: 'Once Function', + text: 'Create a function that only runs once, returning cached result on subsequent calls.', + setup: 'let counter = 0;', + setupCode: 'let counter = 0;', + expected: { results: [1, 1, 1], counter: 1 }, + sample: 'const once = fn => { let called = false, result; return (...args) => called ? result : (called = true, result = fn(...args)); }; const increment = once(() => ++counter); const results = [increment(), increment(), increment()]; ({ results, counter })', + hints: ['Track if function was called', 'Cache and return the result'], + tags: ['functional', 'once', 'closure'], + }, + { + id: 'js-functional-035', + category: 'Functional Programming', + difficulty: 'medium', + title: 'Tap Function', + text: 'Create a tap function for side effects in a pipeline.', + setup: 'let logged = []; const nums = [1, 2, 3];', + setupCode: 'let logged = []; const nums = [1, 2, 3];', + expected: { result: [2, 4, 6], logged: [2, 4, 6] }, + sample: 'const tap = fn => x => { fn(x); return x; }; const result = nums.map(x => x * 2).map(tap(x => logged.push(x))); ({ result, logged })', + hints: ['Execute side effect', 'Return original value unchanged'], + tags: ['functional', 'tap', 'side-effect'], + }, + { + id: 'js-functional-036', + category: 'Recursion', + difficulty: 'medium', + title: 'Recursive Sum', + text: 'Calculate sum of array using recursion.', + setup: 'const nums = [1, 2, 3, 4, 5];', + setupCode: 'const nums = [1, 2, 3, 4, 5];', + expected: 15, + sample: 'const sum = ([head, ...tail]) => head === undefined ? 0 : head + sum(tail); sum(nums)', + hints: ['Base case: empty array returns 0', 'Recursive: head plus sum of tail'], + tags: ['functional', 'recursion', 'sum'], + }, + { + id: 'js-functional-037', + category: 'Recursion', + difficulty: 'medium', + title: 'Recursive Map', + text: 'Implement map using recursion.', + setup: 'const nums = [1, 2, 3];', + setupCode: 'const nums = [1, 2, 3];', + expected: [2, 4, 6], + sample: 'const map = (fn, [head, ...tail]) => head === undefined ? [] : [fn(head), ...map(fn, tail)]; map(x => x * 2, nums)', + hints: ['Transform head, recurse on tail', 'Base case returns empty array'], + tags: ['functional', 'recursion', 'map'], + }, + { + id: 'js-functional-038', + category: 'Recursion', + difficulty: 'medium', + title: 'Recursive Filter', + text: 'Implement filter using recursion.', + setup: 'const nums = [1, 2, 3, 4, 5, 6];', + setupCode: 'const nums = [1, 2, 3, 4, 5, 6];', + expected: [2, 4, 6], + sample: 'const filter = (pred, [head, ...tail]) => head === undefined ? [] : pred(head) ? [head, ...filter(pred, tail)] : filter(pred, tail); filter(x => x % 2 === 0, nums)', + hints: ['Include head only if predicate passes', 'Always recurse on tail'], + tags: ['functional', 'recursion', 'filter'], + }, + { + id: 'js-functional-039', + category: 'Functional Programming', + difficulty: 'medium', + title: 'Zip Arrays', + text: 'Combine two arrays element-wise into pairs.', + setup: 'const a = [1, 2, 3]; const b = ["a", "b", "c"];', + setupCode: 'const a = [1, 2, 3]; const b = ["a", "b", "c"];', + expected: [[1, 'a'], [2, 'b'], [3, 'c']], + sample: 'a.map((x, i) => [x, b[i]])', + hints: ['Use index to access corresponding element', 'Map creates the pairs'], + tags: ['functional', 'zip', 'array'], + }, + { + id: 'js-functional-040', + category: 'Functional Programming', + difficulty: 'medium', + title: 'Unzip Pairs', + text: 'Split an array of pairs into two separate arrays.', + setup: 'const pairs = [[1, "a"], [2, "b"], [3, "c"]];', + setupCode: 'const pairs = [[1, "a"], [2, "b"], [3, "c"]];', + expected: [[1, 2, 3], ['a', 'b', 'c']], + sample: 'pairs.reduce((acc, [a, b]) => [[...acc[0], a], [...acc[1], b]], [[], []])', + hints: ['Accumulate into two arrays', 'Destructure each pair'], + tags: ['functional', 'unzip', 'reduce'], + }, + { + id: 'js-functional-041', + category: 'Higher-Order Functions', + difficulty: 'medium', + title: 'Partition Array', + text: 'Split array into two based on predicate - passing and failing.', + setup: 'const nums = [1, 2, 3, 4, 5, 6];', + setupCode: 'const nums = [1, 2, 3, 4, 5, 6];', + expected: [[2, 4, 6], [1, 3, 5]], + sample: 'const partition = (pred, arr) => arr.reduce(([pass, fail], x) => pred(x) ? [[...pass, x], fail] : [pass, [...fail, x]], [[], []]); partition(x => x % 2 === 0, nums)', + hints: ['Track two arrays in accumulator', 'Add to appropriate array based on predicate'], + tags: ['functional', 'partition', 'reduce'], + }, + { + id: 'js-functional-042', + category: 'Functional Programming', + difficulty: 'medium', + title: 'Pipe Multiple Functions', + text: 'Create a pipe function that composes multiple functions left-to-right.', + setup: 'const add1 = x => x + 1; const mult2 = x => x * 2; const sub3 = x => x - 3;', + setupCode: 'const add1 = x => x + 1; const mult2 = x => x * 2; const sub3 = x => x - 3;', + expected: 9, + sample: 'const pipe = (...fns) => x => fns.reduce((v, fn) => fn(v), x); pipe(add1, mult2, sub3)(5)', + hints: ['reduce over functions', 'Each function transforms the value'], + tags: ['functional', 'pipe', 'reduce'], + }, + { + id: 'js-functional-043', + category: 'Functional Programming', + difficulty: 'medium', + title: 'Compose Multiple Functions', + text: 'Create a compose function that composes multiple functions right-to-left.', + setup: 'const add1 = x => x + 1; const mult2 = x => x * 2; const sub3 = x => x - 3;', + setupCode: 'const add1 = x => x + 1; const mult2 = x => x * 2; const sub3 = x => x - 3;', + expected: 5, + sample: 'const compose = (...fns) => x => fns.reduceRight((v, fn) => fn(v), x); compose(add1, mult2, sub3)(5)', + hints: ['reduceRight for right-to-left', 'Same as pipe but reversed'], + tags: ['functional', 'compose', 'reduceRight'], + }, + { + id: 'js-functional-044', + category: 'Functional Programming', + difficulty: 'medium', + title: 'Deep Clone Object', + text: 'Create a deep clone without mutation using JSON methods.', + setup: 'const obj = { a: 1, b: { c: 2 } };', + setupCode: 'const obj = { a: 1, b: { c: 2 } };', + expected: true, + sample: 'const clone = JSON.parse(JSON.stringify(obj)); clone.b.c = 99; obj.b.c === 2', + hints: ['JSON stringify then parse', 'Creates completely new object'], + tags: ['functional', 'immutability', 'clone'], + }, + { + id: 'js-functional-045', + category: 'Higher-Order Functions', + difficulty: 'medium', + title: 'Take N Elements', + text: 'Create a function that takes the first n elements.', + setup: 'const nums = [1, 2, 3, 4, 5];', + setupCode: 'const nums = [1, 2, 3, 4, 5];', + expected: [1, 2, 3], + sample: 'const take = n => arr => arr.slice(0, n); take(3)(nums)', + hints: ['slice does not mutate', 'Curried for partial application'], + tags: ['functional', 'take', 'slice'], + }, + { + id: 'js-functional-046', + category: 'Higher-Order Functions', + difficulty: 'medium', + title: 'Drop N Elements', + text: 'Create a function that drops the first n elements.', + setup: 'const nums = [1, 2, 3, 4, 5];', + setupCode: 'const nums = [1, 2, 3, 4, 5];', + expected: [4, 5], + sample: 'const drop = n => arr => arr.slice(n); drop(3)(nums)', + hints: ['slice from index n to end', 'Curried for composition'], + tags: ['functional', 'drop', 'slice'], + }, + { + id: 'js-functional-047', + category: 'Functional Programming', + difficulty: 'medium', + title: 'Prop Getter', + text: 'Create a curried function to get a property from objects.', + setup: 'const users = [{ name: "Alice" }, { name: "Bob" }];', + setupCode: 'const users = [{ name: "Alice" }, { name: "Bob" }];', + expected: ['Alice', 'Bob'], + sample: 'const prop = key => obj => obj[key]; users.map(prop("name"))', + hints: ['Return function that accesses the key', 'Perfect for mapping'], + tags: ['functional', 'prop', 'currying'], + }, + { + id: 'js-functional-048', + category: 'Functional Programming', + difficulty: 'medium', + title: 'Pluck Values', + text: 'Extract a specific property from each object in array.', + setup: 'const items = [{ id: 1, val: "a" }, { id: 2, val: "b" }];', + setupCode: 'const items = [{ id: 1, val: "a" }, { id: 2, val: "b" }];', + expected: [1, 2], + sample: 'const pluck = key => arr => arr.map(obj => obj[key]); pluck("id")(items)', + hints: ['Combine map with property access', 'Curried for reusability'], + tags: ['functional', 'pluck', 'map'], + }, + { + id: 'js-functional-049', + category: 'Higher-Order Functions', + difficulty: 'medium', + title: 'Sort By Property', + text: 'Create a curried sort function for object properties.', + setup: 'const users = [{ name: "Charlie" }, { name: "Alice" }, { name: "Bob" }];', + setupCode: 'const users = [{ name: "Charlie" }, { name: "Alice" }, { name: "Bob" }];', + expected: [{ name: 'Alice' }, { name: 'Bob' }, { name: 'Charlie' }], + sample: 'const sortBy = key => arr => [...arr].sort((a, b) => a[key] > b[key] ? 1 : -1); sortBy("name")(users)', + hints: ['Copy array before sorting', 'Compare property values'], + tags: ['functional', 'sort', 'immutability'], + }, + { + id: 'js-functional-050', + category: 'Higher-Order Functions', + difficulty: 'medium', + title: 'Find Index with Predicate', + text: 'Find the index of first element matching a predicate.', + setup: 'const nums = [1, 3, 5, 8, 9];', + setupCode: 'const nums = [1, 3, 5, 8, 9];', + expected: 3, + sample: 'nums.findIndex(x => x % 2 === 0)', + hints: ['findIndex returns index not element', 'Returns -1 if not found'], + tags: ['functional', 'findIndex', 'predicate'], + }, + { + id: 'js-functional-051', + category: 'Functional Programming', + difficulty: 'medium', + title: 'Range Generator', + text: 'Create a function that generates a range of numbers.', + setup: 'let range;', + setupCode: 'let range;', + expected: [0, 1, 2, 3, 4], + sample: 'range = Array.from({ length: 5 }, (_, i) => i)', + hints: ['Array.from takes a length', 'Map function receives index'], + tags: ['functional', 'range', 'generator'], + }, + { + id: 'js-functional-052', + category: 'Closures', + difficulty: 'medium', + title: 'Accumulator Closure', + text: 'Create a function that accumulates values over multiple calls.', + setup: 'let result;', + setupCode: 'let result;', + expected: [5, 15, 35], + sample: 'const makeAccumulator = () => { let total = 0; return n => total += n; }; const acc = makeAccumulator(); result = [acc(5), acc(10), acc(20)]', + hints: ['Closure maintains running total', 'Each call adds to total'], + tags: ['functional', 'closure', 'accumulator'], + }, + { + id: 'js-functional-053', + category: 'Functional Programming', + difficulty: 'medium', + title: 'Object From Entries', + text: 'Convert key-value entries back to object functionally.', + setup: 'const entries = [["x", 10], ["y", 20]];', + setupCode: 'const entries = [["x", 10], ["y", 20]];', + expected: { x: 10, y: 20 }, + sample: 'Object.fromEntries(entries)', + hints: ['Inverse of Object.entries', 'Built-in functional method'], + tags: ['functional', 'object', 'fromEntries'], + }, + { + id: 'js-functional-054', + category: 'Functional Programming', + difficulty: 'medium', + title: 'Transform Object Values', + text: 'Double all values in an object without mutation.', + setup: 'const obj = { a: 1, b: 2, c: 3 };', + setupCode: 'const obj = { a: 1, b: 2, c: 3 };', + expected: { a: 2, b: 4, c: 6 }, + sample: 'Object.fromEntries(Object.entries(obj).map(([k, v]) => [k, v * 2]))', + hints: ['Convert to entries, transform, convert back', 'Entries are [key, value] pairs'], + tags: ['functional', 'object', 'transformation'], + }, + { + id: 'js-functional-055', + category: 'Higher-Order Functions', + difficulty: 'medium', + title: 'Filter Object Keys', + text: 'Keep only object keys that start with underscore.', + setup: 'const obj = { _id: 1, name: "test", _type: "user" };', + setupCode: 'const obj = { _id: 1, name: "test", _type: "user" };', + expected: { _id: 1, _type: 'user' }, + sample: 'Object.fromEntries(Object.entries(obj).filter(([k]) => k.startsWith("_")))', + hints: ['Filter entries by key', 'Destructure to get key only'], + tags: ['functional', 'object', 'filter'], + }, + { + id: 'js-functional-056', + category: 'Functional Programming', + difficulty: 'hard', + title: 'Generic Curry Function', + text: 'Implement a curry function that works with any number of arguments.', + setup: 'const add4 = (a, b, c, d) => a + b + c + d;', + setupCode: 'const add4 = (a, b, c, d) => a + b + c + d;', + expected: 10, + sample: 'const curry = fn => { const arity = fn.length; return function curried(...args) { return args.length >= arity ? fn(...args) : (...more) => curried(...args, ...more); }; }; curry(add4)(1)(2)(3)(4)', + hints: ['Check if enough arguments received', 'Recursively collect arguments'], + tags: ['functional', 'currying', 'arity'], + }, + { + id: 'js-functional-057', + category: 'Functional Programming', + difficulty: 'hard', + title: 'Uncurry Function', + text: 'Convert a curried function back to regular multi-argument form.', + setup: 'const curriedAdd = a => b => c => a + b + c;', + setupCode: 'const curriedAdd = a => b => c => a + b + c;', + expected: 6, + sample: 'const uncurry = fn => (...args) => args.reduce((f, arg) => f(arg), fn); uncurry(curriedAdd)(1, 2, 3)', + hints: ['Apply arguments one at a time', 'reduce over arguments'], + tags: ['functional', 'uncurry', 'reduce'], + }, + { + id: 'js-functional-058', + category: 'Functional Programming', + difficulty: 'hard', + title: 'Memoize with Multiple Arguments', + text: 'Create a memoization function that handles multiple arguments.', + setup: 'let calls = 0; const expensive = (a, b) => { calls++; return a + b; };', + setupCode: 'let calls = 0; const expensive = (a, b) => { calls++; return a + b; };', + expected: { results: [3, 3, 7], calls: 2 }, + sample: 'const memoize = fn => { const cache = new Map(); return (...args) => { const key = JSON.stringify(args); if (cache.has(key)) return cache.get(key); const result = fn(...args); cache.set(key, result); return result; }; }; const memo = memoize(expensive); const results = [memo(1, 2), memo(1, 2), memo(3, 4)]; ({ results, calls })', + hints: ['Serialize arguments as cache key', 'Use Map for the cache'], + tags: ['functional', 'memoization', 'map'], + }, + { + id: 'js-functional-059', + category: 'Higher-Order Functions', + difficulty: 'hard', + title: 'Debounce Function', + text: 'Implement a debounce that delays function execution.', + setup: 'let calls = [];', + setupCode: 'let calls = [];', + expected: true, + sample: 'const debounce = (fn, delay) => { let timeout; return (...args) => { clearTimeout(timeout); timeout = setTimeout(() => fn(...args), delay); }; }; typeof debounce(() => {}, 100) === "function"', + hints: ['Clear previous timeout', 'Set new timeout each call'], + tags: ['functional', 'debounce', 'timing'], + }, + { + id: 'js-functional-060', + category: 'Higher-Order Functions', + difficulty: 'hard', + title: 'Throttle Function', + text: 'Implement a throttle that limits function calls.', + setup: 'let result;', + setupCode: 'let result;', + expected: true, + sample: 'const throttle = (fn, limit) => { let waiting = false; return (...args) => { if (!waiting) { fn(...args); waiting = true; setTimeout(() => waiting = false, limit); } }; }; result = typeof throttle(() => {}, 100) === "function"', + hints: ['Track if in waiting period', 'Reset flag after delay'], + tags: ['functional', 'throttle', 'timing'], + }, + { + id: 'js-functional-061', + category: 'Recursion', + difficulty: 'hard', + title: 'Deep Flatten Array', + text: 'Flatten a deeply nested array using recursion.', + setup: 'const nested = [1, [2, [3, [4, [5]]]]];', + setupCode: 'const nested = [1, [2, [3, [4, [5]]]]];', + expected: [1, 2, 3, 4, 5], + sample: 'const deepFlatten = arr => arr.reduce((acc, val) => acc.concat(Array.isArray(val) ? deepFlatten(val) : val), []); deepFlatten(nested)', + hints: ['Check if element is array', 'Recursively flatten nested arrays'], + tags: ['functional', 'recursion', 'flatten'], + }, + { + id: 'js-functional-062', + category: 'Recursion', + difficulty: 'hard', + title: 'Recursive Reduce', + text: 'Implement reduce using recursion.', + setup: 'const nums = [1, 2, 3, 4];', + setupCode: 'const nums = [1, 2, 3, 4];', + expected: 10, + sample: 'const reduce = (fn, acc, [head, ...tail]) => head === undefined ? acc : reduce(fn, fn(acc, head), tail); reduce((a, b) => a + b, 0, nums)', + hints: ['Apply function to accumulator and head', 'Pass new accumulator to recursive call'], + tags: ['functional', 'recursion', 'reduce'], + }, + { + id: 'js-functional-063', + category: 'Functional Programming', + difficulty: 'hard', + title: 'Transducer Map', + text: 'Create a mapping transducer that composes with reduce.', + setup: 'const nums = [1, 2, 3];', + setupCode: 'const nums = [1, 2, 3];', + expected: [2, 4, 6], + sample: 'const mapT = fn => reducer => (acc, x) => reducer(acc, fn(x)); const push = (arr, x) => [...arr, x]; nums.reduce(mapT(x => x * 2)(push), [])', + hints: ['Transducer wraps a reducer', 'Transform value before passing to reducer'], + tags: ['functional', 'transducer', 'map'], + }, + { + id: 'js-functional-064', + category: 'Functional Programming', + difficulty: 'hard', + title: 'Transducer Filter', + text: 'Create a filtering transducer.', + setup: 'const nums = [1, 2, 3, 4, 5, 6];', + setupCode: 'const nums = [1, 2, 3, 4, 5, 6];', + expected: [2, 4, 6], + sample: 'const filterT = pred => reducer => (acc, x) => pred(x) ? reducer(acc, x) : acc; const push = (arr, x) => [...arr, x]; nums.reduce(filterT(x => x % 2 === 0)(push), [])', + hints: ['Only call reducer if predicate passes', 'Return unchanged accumulator otherwise'], + tags: ['functional', 'transducer', 'filter'], + }, + { + id: 'js-functional-065', + category: 'Functional Programming', + difficulty: 'hard', + title: 'Compose Transducers', + text: 'Compose multiple transducers together.', + setup: 'const nums = [1, 2, 3, 4, 5, 6];', + setupCode: 'const nums = [1, 2, 3, 4, 5, 6];', + expected: [4, 8, 12], + sample: 'const mapT = fn => r => (a, x) => r(a, fn(x)); const filterT = p => r => (a, x) => p(x) ? r(a, x) : a; const comp = (...fns) => fns.reduce((f, g) => x => f(g(x))); const push = (arr, x) => [...arr, x]; nums.reduce(comp(filterT(x => x % 2 === 0), mapT(x => x * 2))(push), [])', + hints: ['Compose transducers like functions', 'Order matters for transformation'], + tags: ['functional', 'transducer', 'compose'], + }, + { + id: 'js-functional-066', + category: 'Functional Programming', + difficulty: 'hard', + title: 'Functor Map', + text: 'Create a simple functor (container) with map method.', + setup: 'let result;', + setupCode: 'let result;', + expected: 10, + sample: 'const Box = x => ({ map: fn => Box(fn(x)), fold: fn => fn(x) }); result = Box(5).map(x => x * 2).fold(x => x)', + hints: ['Box wraps a value', 'map returns new Box with transformed value'], + tags: ['functional', 'functor', 'container'], + }, + { + id: 'js-functional-067', + category: 'Functional Programming', + difficulty: 'hard', + title: 'Maybe Functor', + text: 'Implement a Maybe functor for null-safe operations.', + setup: 'let results;', + setupCode: 'let results;', + expected: ['HELLO', 'nothing'], + sample: 'const Maybe = x => ({ map: fn => x == null ? Maybe(null) : Maybe(fn(x)), fold: (onNothing, onJust) => x == null ? onNothing() : onJust(x) }); const r1 = Maybe("hello").map(s => s.toUpperCase()).fold(() => "nothing", x => x); const r2 = Maybe(null).map(s => s.toUpperCase()).fold(() => "nothing", x => x); results = [r1, r2]', + hints: ['Check for null before mapping', 'fold handles both cases'], + tags: ['functional', 'maybe', 'functor'], + }, + { + id: 'js-functional-068', + category: 'Functional Programming', + difficulty: 'hard', + title: 'Either Functor', + text: 'Implement Either for error handling.', + setup: 'let results;', + setupCode: 'let results;', + expected: [10, 'Error: Division by zero'], + sample: 'const Right = x => ({ map: fn => Right(fn(x)), fold: (f, g) => g(x) }); const Left = x => ({ map: fn => Left(x), fold: (f, g) => f(x) }); const safeDivide = (a, b) => b === 0 ? Left("Division by zero") : Right(a / b); const r1 = safeDivide(20, 2).fold(e => `Error: ${e}`, x => x); const r2 = safeDivide(20, 0).fold(e => `Error: ${e}`, x => x); results = [r1, r2]', + hints: ['Right maps and folds with success handler', 'Left ignores map and folds with error handler'], + tags: ['functional', 'either', 'error-handling'], + }, + { + id: 'js-functional-069', + category: 'Functional Programming', + difficulty: 'hard', + title: 'Chain/FlatMap for Maybe', + text: 'Add chain method to Maybe for nested operations.', + setup: 'let result;', + setupCode: 'let result;', + expected: 'John Doe', + sample: 'const Maybe = x => ({ map: fn => x == null ? Maybe(null) : Maybe(fn(x)), chain: fn => x == null ? Maybe(null) : fn(x), fold: (n, j) => x == null ? n() : j(x) }); const user = { profile: { name: "John Doe" } }; result = Maybe(user).chain(u => Maybe(u.profile)).chain(p => Maybe(p.name)).fold(() => "No name", x => x)', + hints: ['chain avoids nested Maybes', 'Similar to flatMap'], + tags: ['functional', 'maybe', 'chain', 'monad'], + }, + { + id: 'js-functional-070', + category: 'Recursion', + difficulty: 'hard', + title: 'Tail-Call Optimized Factorial', + text: 'Implement factorial using tail recursion pattern.', + setup: 'let result;', + setupCode: 'let result;', + expected: 120, + sample: 'const factorial = (n, acc = 1) => n <= 1 ? acc : factorial(n - 1, n * acc); result = factorial(5)', + hints: ['Accumulator carries result', 'Last operation is recursive call'], + tags: ['functional', 'recursion', 'tail-call'], + }, + { + id: 'js-functional-071', + category: 'Recursion', + difficulty: 'hard', + title: 'Trampoline for Stack Safety', + text: 'Implement trampoline to avoid stack overflow.', + setup: 'let result;', + setupCode: 'let result;', + expected: 50005000, + sample: 'const trampoline = fn => (...args) => { let result = fn(...args); while (typeof result === "function") result = result(); return result; }; const sumTo = trampoline((n, acc = 0) => n === 0 ? acc : () => sumTo(n - 1, acc + n)); result = sumTo(10000)', + hints: ['Return thunk instead of recursing', 'Loop while result is function'], + tags: ['functional', 'trampoline', 'stack-safe'], + }, + { + id: 'js-functional-072', + category: 'Higher-Order Functions', + difficulty: 'hard', + title: 'Async Compose', + text: 'Compose async functions that return promises.', + setup: 'let result;', + setupCode: 'let result;', + expected: 8, + sample: 'const asyncCompose = (...fns) => x => fns.reduceRight((p, fn) => p.then(fn), Promise.resolve(x)); const add1 = async x => x + 1; const mult2 = async x => x * 2; result = await asyncCompose(mult2, add1, add1)(2)', + hints: ['Chain promises with then', 'reduceRight for right-to-left'], + tags: ['functional', 'async', 'compose'], + }, + { + id: 'js-functional-073', + category: 'Higher-Order Functions', + difficulty: 'hard', + title: 'Async Pipe', + text: 'Pipe async functions left-to-right.', + setup: 'let result;', + setupCode: 'let result;', + expected: 7, + sample: 'const asyncPipe = (...fns) => x => fns.reduce((p, fn) => p.then(fn), Promise.resolve(x)); const add1 = async x => x + 1; const mult2 = async x => x * 2; result = await asyncPipe(add1, mult2, add1)(2)', + hints: ['reduce for left-to-right', 'Each then chains next function'], + tags: ['functional', 'async', 'pipe'], + }, + { + id: 'js-functional-074', + category: 'Functional Programming', + difficulty: 'hard', + title: 'Lens Getter', + text: 'Implement a lens getter for nested property access.', + setup: 'const data = { user: { profile: { name: "Alice" } } };', + setupCode: 'const data = { user: { profile: { name: "Alice" } } };', + expected: 'Alice', + sample: 'const lens = path => ({ get: obj => path.reduce((o, k) => o && o[k], obj) }); lens(["user", "profile", "name"]).get(data)', + hints: ['Reduce over path segments', 'Handle undefined safely'], + tags: ['functional', 'lens', 'getter'], + }, + { + id: 'js-functional-075', + category: 'Functional Programming', + difficulty: 'hard', + title: 'Lens Setter', + text: 'Implement a lens setter for immutable nested updates.', + setup: 'const data = { a: { b: { c: 1 } } };', + setupCode: 'const data = { a: { b: { c: 1 } } };', + expected: { a: { b: { c: 2 } } }, + sample: 'const setPath = (obj, [head, ...tail], val) => ({ ...obj, [head]: tail.length ? setPath(obj[head] || {}, tail, val) : val }); setPath(data, ["a", "b", "c"], 2)', + hints: ['Recursively rebuild object path', 'Spread existing properties'], + tags: ['functional', 'lens', 'setter', 'immutability'], + }, + { + id: 'js-functional-076', + category: 'Functional Programming', + difficulty: 'hard', + title: 'Point-Free Style', + text: 'Refactor to point-free style using compose.', + setup: 'const users = [{ name: "alice" }, { name: "bob" }];', + setupCode: 'const users = [{ name: "alice" }, { name: "bob" }];', + expected: ['ALICE', 'BOB'], + sample: 'const compose = (...fns) => x => fns.reduceRight((v, f) => f(v), x); const prop = k => o => o[k]; const toUpper = s => s.toUpperCase(); const getName = compose(toUpper, prop("name")); users.map(getName)', + hints: ['Remove explicit parameter references', 'Build from smaller functions'], + tags: ['functional', 'point-free', 'compose'], + }, + { + id: 'js-functional-077', + category: 'Higher-Order Functions', + difficulty: 'hard', + title: 'Apply to Arguments', + text: 'Create a function that applies an array of functions to corresponding arguments.', + setup: 'const fns = [x => x + 1, x => x * 2, x => x - 1]; const args = [1, 2, 3];', + setupCode: 'const fns = [x => x + 1, x => x * 2, x => x - 1]; const args = [1, 2, 3];', + expected: [2, 4, 2], + sample: 'fns.map((fn, i) => fn(args[i]))', + hints: ['Use index to pair function with argument', 'Map over functions array'], + tags: ['functional', 'applicative', 'map'], + }, + { + id: 'js-functional-078', + category: 'Functional Programming', + difficulty: 'hard', + title: 'Converge Pattern', + text: 'Apply multiple functions to same input and combine results.', + setup: 'const nums = [1, 2, 3, 4, 5];', + setupCode: 'const nums = [1, 2, 3, 4, 5];', + expected: 3, + sample: 'const converge = (combiner, fns) => (...args) => combiner(...fns.map(fn => fn(...args))); const sum = arr => arr.reduce((a, b) => a + b, 0); const len = arr => arr.length; const average = converge((s, l) => s / l, [sum, len]); average(nums)', + hints: ['Apply all functions to input', 'Pass results to combiner'], + tags: ['functional', 'converge', 'combinator'], + }, + { + id: 'js-functional-079', + category: 'Functional Programming', + difficulty: 'hard', + title: 'Use With Pattern', + text: 'Create a useWith combinator that transforms arguments before applying.', + setup: 'const fn = (a, b) => a + b;', + setupCode: 'const fn = (a, b) => a + b;', + expected: 7, + sample: 'const useWith = (fn, transformers) => (...args) => fn(...args.map((arg, i) => transformers[i] ? transformers[i](arg) : arg)); useWith(fn, [x => x * 2, x => x + 1])(2, 2)', + hints: ['Transform each argument with corresponding function', 'Handle missing transformers'], + tags: ['functional', 'useWith', 'combinator'], + }, + { + id: 'js-functional-080', + category: 'Recursion', + difficulty: 'hard', + title: 'Y Combinator', + text: 'Implement the Y combinator for anonymous recursion.', + setup: 'let result;', + setupCode: 'let result;', + expected: 120, + sample: 'const Y = f => (x => f(v => x(x)(v)))(x => f(v => x(x)(v))); const factorial = Y(f => n => n <= 1 ? 1 : n * f(n - 1)); result = factorial(5)', + hints: ['Y enables recursion without naming', 'Pass function reference as parameter'], + tags: ['functional', 'y-combinator', 'recursion'], + }, + { + id: 'js-functional-081', + category: 'Higher-Order Functions', + difficulty: 'hard', + title: 'Unfold Generator', + text: 'Create an unfold function that generates arrays from a seed.', + setup: 'let result;', + setupCode: 'let result;', + expected: [1, 2, 3, 4, 5], + sample: 'const unfold = (fn, seed) => { const result = []; let val = fn(seed); while (val) { result.push(val[0]); val = fn(val[1]); } return result; }; result = unfold(n => n > 5 ? null : [n, n + 1], 1)', + hints: ['Generate value and next seed', 'Return null to stop'], + tags: ['functional', 'unfold', 'generator'], + }, + { + id: 'js-functional-082', + category: 'Functional Programming', + difficulty: 'hard', + title: 'Reader Monad Pattern', + text: 'Implement a Reader pattern for dependency injection.', + setup: 'const config = { multiplier: 3 };', + setupCode: 'const config = { multiplier: 3 };', + expected: 15, + sample: 'const Reader = run => ({ run, map: fn => Reader(env => fn(run(env))), chain: fn => Reader(env => fn(run(env)).run(env)) }); const getMultiplier = Reader(cfg => cfg.multiplier); const compute = x => getMultiplier.map(m => x * m); compute(5).run(config)', + hints: ['Reader delays access to environment', 'run injects the dependency'], + tags: ['functional', 'reader', 'monad'], + }, + { + id: 'js-functional-083', + category: 'Closures', + difficulty: 'hard', + title: 'Module Pattern', + text: 'Create a module with private state using closures.', + setup: 'let result;', + setupCode: 'let result;', + expected: { count: 3, items: ['a', 'b', 'c'] }, + sample: 'const createModule = () => { const items = []; return { add: item => items.push(item), getItems: () => [...items], count: () => items.length }; }; const mod = createModule(); mod.add("a"); mod.add("b"); mod.add("c"); result = { count: mod.count(), items: mod.getItems() }', + hints: ['Private array hidden in closure', 'Public methods access private state'], + tags: ['functional', 'module', 'closure'], + }, + { + id: 'js-functional-084', + category: 'Functional Programming', + difficulty: 'hard', + title: 'Immutable Stack', + text: 'Implement an immutable stack data structure.', + setup: 'let result;', + setupCode: 'let result;', + expected: { top: 3, size: 3 }, + sample: 'const Stack = (items = []) => ({ push: x => Stack([...items, x]), pop: () => Stack(items.slice(0, -1)), peek: () => items[items.length - 1], size: () => items.length }); const stack = Stack().push(1).push(2).push(3); result = { top: stack.peek(), size: stack.size() }', + hints: ['Each operation returns new Stack', 'Never mutate internal array'], + tags: ['functional', 'immutability', 'data-structure'], + }, + { + id: 'js-functional-085', + category: 'Functional Programming', + difficulty: 'hard', + title: 'Lazy Evaluation', + text: 'Implement lazy evaluation for deferred computation.', + setup: 'let computed = 0;', + setupCode: 'let computed = 0;', + expected: { value: 10, computedOnce: 1 }, + sample: 'const lazy = fn => { let cached, done = false; return () => done ? cached : (done = true, cached = fn()); }; const getValue = lazy(() => { computed++; return 10; }); getValue(); getValue(); getValue(); ({ value: getValue(), computedOnce: computed })', + hints: ['Cache result after first call', 'Track if computation happened'], + tags: ['functional', 'lazy', 'memoization'], + }, + { + id: 'js-functional-086', + category: 'Higher-Order Functions', + difficulty: 'easy', + title: 'Call N Times', + text: 'Create a function that calls another function n times.', + setup: 'let count = 0;', + setupCode: 'let count = 0;', + expected: 5, + sample: 'const times = (n, fn) => { for (let i = 0; i < n; i++) fn(i); }; times(5, () => count++); count', + hints: ['Loop n times', 'Optionally pass index to function'], + tags: ['functional', 'times', 'iteration'], + }, + { + id: 'js-functional-087', + category: 'Functional Programming', + difficulty: 'easy', + title: 'Default Value', + text: 'Create a function that provides a default value for nullish values.', + setup: 'const values = [null, 5, undefined, 10];', + setupCode: 'const values = [null, 5, undefined, 10];', + expected: [0, 5, 0, 10], + sample: 'const defaultTo = def => val => val ?? def; values.map(defaultTo(0))', + hints: ['Nullish coalescing handles null/undefined', 'Curried for mapping'], + tags: ['functional', 'default', 'nullish'], + }, + { + id: 'js-functional-088', + category: 'Higher-Order Functions', + difficulty: 'easy', + title: 'Chunk Array', + text: 'Split an array into chunks of specified size.', + setup: 'const nums = [1, 2, 3, 4, 5, 6, 7];', + setupCode: 'const nums = [1, 2, 3, 4, 5, 6, 7];', + expected: [[1, 2, 3], [4, 5, 6], [7]], + sample: 'const chunk = (arr, size) => arr.reduce((chunks, item, i) => { const idx = Math.floor(i / size); chunks[idx] = [...(chunks[idx] || []), item]; return chunks; }, []); chunk(nums, 3)', + hints: ['Calculate chunk index from item index', 'Build chunks in reducer'], + tags: ['functional', 'chunk', 'reduce'], + }, + { + id: 'js-functional-089', + category: 'Functional Programming', + difficulty: 'easy', + title: 'Clamp Value', + text: 'Clamp a value between min and max.', + setup: 'const values = [-5, 0, 5, 10, 15];', + setupCode: 'const values = [-5, 0, 5, 10, 15];', + expected: [0, 0, 5, 10, 10], + sample: 'const clamp = (min, max) => val => Math.min(Math.max(val, min), max); values.map(clamp(0, 10))', + hints: ['Max with min first', 'Then min with max'], + tags: ['functional', 'clamp', 'currying'], + }, + { + id: 'js-functional-090', + category: 'Higher-Order Functions', + difficulty: 'easy', + title: 'Unique Values', + text: 'Get unique values from array using functional approach.', + setup: 'const nums = [1, 2, 2, 3, 3, 3, 4];', + setupCode: 'const nums = [1, 2, 2, 3, 3, 3, 4];', + expected: [1, 2, 3, 4], + sample: '[...new Set(nums)]', + hints: ['Set automatically removes duplicates', 'Spread back to array'], + tags: ['functional', 'unique', 'set'], + }, + { + id: 'js-functional-091', + category: 'Functional Programming', + difficulty: 'easy', + title: 'Boolean Coercion', + text: 'Filter out falsy values from an array.', + setup: 'const mixed = [0, 1, false, 2, "", 3, null, undefined];', + setupCode: 'const mixed = [0, 1, false, 2, "", 3, null, undefined];', + expected: [1, 2, 3], + sample: 'mixed.filter(Boolean)', + hints: ['Boolean as function coerces to boolean', 'Falsy values become false'], + tags: ['functional', 'filter', 'boolean'], + }, + { + id: 'js-functional-092', + category: 'Functional Programming', + difficulty: 'easy', + title: 'Count Occurrences', + text: 'Count occurrences of each element in array.', + setup: 'const items = ["a", "b", "a", "c", "b", "a"];', + setupCode: 'const items = ["a", "b", "a", "c", "b", "a"];', + expected: { a: 3, b: 2, c: 1 }, + sample: 'items.reduce((counts, item) => ({ ...counts, [item]: (counts[item] || 0) + 1 }), {})', + hints: ['Initialize count to 0 if missing', 'Increment for each occurrence'], + tags: ['functional', 'reduce', 'count'], + }, + { + id: 'js-functional-093', + category: 'Higher-Order Functions', + difficulty: 'easy', + title: 'Index Of with Predicate', + text: 'Find index where predicate first returns true.', + setup: 'const nums = [1, 4, 9, 16, 25];', + setupCode: 'const nums = [1, 4, 9, 16, 25];', + expected: 2, + sample: 'nums.findIndex(x => x > 5)', + hints: ['findIndex with predicate', 'Returns -1 if not found'], + tags: ['functional', 'findIndex', 'predicate'], + }, + { + id: 'js-functional-094', + category: 'Functional Programming', + difficulty: 'easy', + title: 'Entries to Object', + text: 'Convert Map entries to plain object.', + setup: 'const map = new Map([["x", 1], ["y", 2]]);', + setupCode: 'const map = new Map([["x", 1], ["y", 2]]);', + expected: { x: 1, y: 2 }, + sample: 'Object.fromEntries(map)', + hints: ['Map is iterable as entries', 'fromEntries converts to object'], + tags: ['functional', 'map', 'object'], + }, + { + id: 'js-functional-095', + category: 'Functional Programming', + difficulty: 'easy', + title: 'Compact Object', + text: 'Remove properties with falsy values from object.', + setup: 'const obj = { a: 1, b: 0, c: null, d: "hello", e: "" };', + setupCode: 'const obj = { a: 1, b: 0, c: null, d: "hello", e: "" };', + expected: { a: 1, d: 'hello' }, + sample: 'Object.fromEntries(Object.entries(obj).filter(([, v]) => Boolean(v)))', + hints: ['Filter entries by value truthiness', 'Destructure to ignore key'], + tags: ['functional', 'object', 'filter'], + }, + { + id: 'js-functional-096', + category: 'Higher-Order Functions', + difficulty: 'medium', + title: 'Reduce Right', + text: 'Use reduceRight to build string from right to left.', + setup: 'const words = ["Hello", "World", "!"];', + setupCode: 'const words = ["Hello", "World", "!"];', + expected: '! World Hello', + sample: 'words.reduceRight((acc, word) => acc + " " + word)', + hints: ['reduceRight starts from last element', 'Accumulates right to left'], + tags: ['functional', 'reduceRight', 'string'], + }, + { + id: 'js-functional-097', + category: 'Closures', + difficulty: 'medium', + title: 'Rate Limiter', + text: 'Create a rate limiter that limits function calls.', + setup: 'let result;', + setupCode: 'let result;', + expected: true, + sample: 'const rateLimit = (fn, limit) => { const calls = []; return (...args) => { const now = Date.now(); while (calls.length && calls[0] < now - 1000) calls.shift(); if (calls.length < limit) { calls.push(now); return fn(...args); } }; }; result = typeof rateLimit(() => {}, 5) === "function"', + hints: ['Track call timestamps', 'Remove old calls outside window'], + tags: ['functional', 'closure', 'rate-limit'], + }, + { + id: 'js-functional-098', + category: 'Functional Programming', + difficulty: 'medium', + title: 'Deep Merge Objects', + text: 'Recursively merge two objects.', + setup: 'const a = { x: 1, y: { z: 2 } }; const b = { y: { w: 3 }, v: 4 };', + setupCode: 'const a = { x: 1, y: { z: 2 } }; const b = { y: { w: 3 }, v: 4 };', + expected: { x: 1, y: { z: 2, w: 3 }, v: 4 }, + sample: 'const deepMerge = (a, b) => { const result = { ...a }; for (const key in b) { result[key] = a[key] && typeof a[key] === "object" && typeof b[key] === "object" ? deepMerge(a[key], b[key]) : b[key]; } return result; }; deepMerge(a, b)', + hints: ['Recurse when both values are objects', 'Otherwise take b value'], + tags: ['functional', 'merge', 'recursion'], + }, + { + id: 'js-functional-099', + category: 'Higher-Order Functions', + difficulty: 'medium', + title: 'Intersection of Arrays', + text: 'Find elements common to both arrays.', + setup: 'const a = [1, 2, 3, 4]; const b = [3, 4, 5, 6];', + setupCode: 'const a = [1, 2, 3, 4]; const b = [3, 4, 5, 6];', + expected: [3, 4], + sample: 'a.filter(x => b.includes(x))', + hints: ['Filter first array', 'Keep elements in second array'], + tags: ['functional', 'intersection', 'filter'], + }, + { + id: 'js-functional-100', + category: 'Higher-Order Functions', + difficulty: 'medium', + title: 'Difference of Arrays', + text: 'Find elements in first array not in second.', + setup: 'const a = [1, 2, 3, 4]; const b = [3, 4, 5, 6];', + setupCode: 'const a = [1, 2, 3, 4]; const b = [3, 4, 5, 6];', + expected: [1, 2], + sample: 'a.filter(x => !b.includes(x))', + hints: ['Filter first array', 'Keep elements NOT in second'], + tags: ['functional', 'difference', 'filter'], + }, + { + id: 'js-functional-101', + category: 'Functional Programming', + difficulty: 'medium', + title: 'Symmetric Difference', + text: 'Find elements in either array but not both.', + setup: 'const a = [1, 2, 3]; const b = [2, 3, 4];', + setupCode: 'const a = [1, 2, 3]; const b = [2, 3, 4];', + expected: [1, 4], + sample: '[...a.filter(x => !b.includes(x)), ...b.filter(x => !a.includes(x))]', + hints: ['Combine both differences', 'Elements exclusive to each array'], + tags: ['functional', 'symmetric-difference', 'array'], + }, + { + id: 'js-functional-102', + category: 'Higher-Order Functions', + difficulty: 'medium', + title: 'Sum By Property', + text: 'Sum array of objects by a specific property.', + setup: 'const items = [{ val: 10 }, { val: 20 }, { val: 30 }];', + setupCode: 'const items = [{ val: 10 }, { val: 20 }, { val: 30 }];', + expected: 60, + sample: 'const sumBy = (arr, key) => arr.reduce((sum, obj) => sum + obj[key], 0); sumBy(items, "val")', + hints: ['Reduce over objects', 'Access property with key'], + tags: ['functional', 'reduce', 'sumBy'], + }, + { + id: 'js-functional-103', + category: 'Functional Programming', + difficulty: 'medium', + title: 'Pick Object Properties', + text: 'Create new object with only specified properties.', + setup: 'const obj = { a: 1, b: 2, c: 3, d: 4 };', + setupCode: 'const obj = { a: 1, b: 2, c: 3, d: 4 };', + expected: { a: 1, c: 3 }, + sample: 'const pick = (obj, keys) => Object.fromEntries(keys.filter(k => k in obj).map(k => [k, obj[k]])); pick(obj, ["a", "c"])', + hints: ['Filter to existing keys', 'Map to entries then convert'], + tags: ['functional', 'pick', 'object'], + }, + { + id: 'js-functional-104', + category: 'Functional Programming', + difficulty: 'medium', + title: 'Omit Object Properties', + text: 'Create new object without specified properties.', + setup: 'const obj = { a: 1, b: 2, c: 3, d: 4 };', + setupCode: 'const obj = { a: 1, b: 2, c: 3, d: 4 };', + expected: { b: 2, d: 4 }, + sample: 'const omit = (obj, keys) => Object.fromEntries(Object.entries(obj).filter(([k]) => !keys.includes(k))); omit(obj, ["a", "c"])', + hints: ['Filter entries by key', 'Exclude keys in the list'], + tags: ['functional', 'omit', 'object'], + }, + { + id: 'js-functional-105', + category: 'Higher-Order Functions', + difficulty: 'medium', + title: 'Map Object Values', + text: 'Apply a function to all values in an object.', + setup: 'const obj = { a: 1, b: 2, c: 3 };', + setupCode: 'const obj = { a: 1, b: 2, c: 3 };', + expected: { a: 2, b: 4, c: 6 }, + sample: 'const mapValues = (obj, fn) => Object.fromEntries(Object.entries(obj).map(([k, v]) => [k, fn(v)])); mapValues(obj, x => x * 2)', + hints: ['Map over entries', 'Transform value, keep key'], + tags: ['functional', 'mapValues', 'object'], + }, + { + id: 'js-functional-106', + category: 'Recursion', + difficulty: 'hard', + title: 'Recursive Object Freeze', + text: 'Deeply freeze an object to make it fully immutable.', + setup: 'const obj = { a: 1, b: { c: 2 } };', + setupCode: 'const obj = { a: 1, b: { c: 2 } };', + expected: true, + sample: 'const deepFreeze = obj => { Object.freeze(obj); Object.values(obj).forEach(v => v && typeof v === "object" && deepFreeze(v)); return obj; }; deepFreeze(obj); Object.isFrozen(obj) && Object.isFrozen(obj.b)', + hints: ['Freeze current object', 'Recursively freeze nested objects'], + tags: ['functional', 'immutability', 'freeze'], + }, + { + id: 'js-functional-107', + category: 'Functional Programming', + difficulty: 'hard', + title: 'State Monad Pattern', + text: 'Implement a simple State monad for stateful computations.', + setup: 'let result;', + setupCode: 'let result;', + expected: { value: 6, state: 3 }, + sample: 'const State = run => ({ run, map: fn => State(s => { const [a, s2] = run(s); return [fn(a), s2]; }), chain: fn => State(s => { const [a, s2] = run(s); return fn(a).run(s2); }) }); const increment = State(s => [s, s + 1]); const compute = increment.chain(a => increment.chain(b => increment.map(c => a + b + c))); result = { value: compute.run(0)[0], state: compute.run(0)[1] }', + hints: ['State wraps function from state to [value, newState]', 'chain threads state through'], + tags: ['functional', 'state', 'monad'], + }, + { + id: 'js-functional-108', + category: 'Functional Programming', + difficulty: 'hard', + title: 'IO Monad Pattern', + text: 'Implement IO monad for side-effect isolation.', + setup: 'let sideEffect = 0;', + setupCode: 'let sideEffect = 0;', + expected: { before: 0, after: 5, result: 10 }, + sample: 'const IO = run => ({ run, map: fn => IO(() => fn(run())), chain: fn => IO(() => fn(run()).run()) }); const before = sideEffect; const io = IO(() => { sideEffect = 5; return 10; }); const result = io.run(); ({ before, after: sideEffect, result })', + hints: ['IO wraps a thunk (function)', 'run executes the side effect'], + tags: ['functional', 'io', 'monad'], + }, + { + id: 'js-functional-109', + category: 'Higher-Order Functions', + difficulty: 'hard', + title: 'Scan (Running Reduce)', + text: 'Implement scan that returns all intermediate reduce values.', + setup: 'const nums = [1, 2, 3, 4, 5];', + setupCode: 'const nums = [1, 2, 3, 4, 5];', + expected: [1, 3, 6, 10, 15], + sample: 'const scan = (fn, init, arr) => arr.reduce((acc, x) => [...acc, fn(acc.length ? acc[acc.length - 1] : init, x)], []); scan((a, b) => a + b, 0, nums)', + hints: ['Store each intermediate result', 'Like reduce but keeps history'], + tags: ['functional', 'scan', 'reduce'], + }, + { + id: 'js-functional-110', + category: 'Functional Programming', + difficulty: 'hard', + title: 'Monad Laws Verification', + text: 'Verify left identity law for a simple monad.', + setup: 'const Box = x => ({ map: f => Box(f(x)), chain: f => f(x), fold: f => f(x) });', + setupCode: 'const Box = x => ({ map: f => Box(f(x)), chain: f => f(x), fold: f => f(x) });', + expected: true, + sample: 'const f = x => Box(x + 1); const a = 5; const leftSide = Box(a).chain(f).fold(x => x); const rightSide = f(a).fold(x => x); leftSide === rightSide', + hints: ['Left identity: M.of(a).chain(f) === f(a)', 'Both sides should produce same result'], + tags: ['functional', 'monad', 'laws'], + }, + + // ======================================== + // DOM MANIPULATION - querySelector/querySelectorAll + // ======================================== + { + id: 'js-dom-001', + category: 'DOM Manipulation', + difficulty: 'easy', + title: 'Select Element by ID', + text: 'Use querySelector to select an element with the id "main-title".', + setup: 'const document = { querySelector: (sel) => sel === "#main-title" ? { id: "main-title", tagName: "H1" } : null };', + setupCode: 'const document = { querySelector: (sel) => sel === "#main-title" ? { id: "main-title", tagName: "H1" } : null };', + expected: { id: 'main-title', tagName: 'H1' }, + sample: 'document.querySelector("#main-title")', + hints: ['Use # prefix for ID selectors', 'querySelector returns the first matching element'], + tags: ['dom', 'querySelector', 'selectors'], + }, + { + id: 'js-dom-002', + category: 'DOM Manipulation', + difficulty: 'easy', + title: 'Select Element by Class', + text: 'Use querySelector to select the first element with class "button".', + setup: 'const document = { querySelector: (sel) => sel === ".button" ? { className: "button", tagName: "BUTTON" } : null };', + setupCode: 'const document = { querySelector: (sel) => sel === ".button" ? { className: "button", tagName: "BUTTON" } : null };', + expected: { className: 'button', tagName: 'BUTTON' }, + sample: 'document.querySelector(".button")', + hints: ['Use . prefix for class selectors', 'querySelector returns the first match'], + tags: ['dom', 'querySelector', 'selectors'], + }, + { + id: 'js-dom-003', + category: 'DOM Manipulation', + difficulty: 'easy', + title: 'Select by Tag Name', + text: 'Use querySelector to select the first paragraph element.', + setup: 'const document = { querySelector: (sel) => sel === "p" ? { tagName: "P", textContent: "Hello" } : null };', + setupCode: 'const document = { querySelector: (sel) => sel === "p" ? { tagName: "P", textContent: "Hello" } : null };', + expected: { tagName: 'P', textContent: 'Hello' }, + sample: 'document.querySelector("p")', + hints: ['Use tag name directly without prefix', 'Returns first matching element'], + tags: ['dom', 'querySelector', 'selectors'], + }, + { + id: 'js-dom-004', + category: 'DOM Manipulation', + difficulty: 'easy', + title: 'Select All Elements by Class', + text: 'Use querySelectorAll to select all elements with class "item" and get the count.', + setup: 'const document = { querySelectorAll: (sel) => sel === ".item" ? { length: 5 } : { length: 0 } };', + setupCode: 'const document = { querySelectorAll: (sel) => sel === ".item" ? { length: 5 } : { length: 0 } };', + expected: 5, + sample: 'document.querySelectorAll(".item").length', + hints: ['querySelectorAll returns a NodeList', 'Use .length to count elements'], + tags: ['dom', 'querySelectorAll', 'selectors'], + }, + { + id: 'js-dom-005', + category: 'DOM Manipulation', + difficulty: 'easy', + title: 'Select with Attribute Selector', + text: 'Select an element with data-type="primary" attribute.', + setup: 'const document = { querySelector: (sel) => sel === \'[data-type="primary"]\' ? { dataset: { type: "primary" } } : null };', + setupCode: 'const document = { querySelector: (sel) => sel === \'[data-type="primary"]\' ? { dataset: { type: "primary" } } : null };', + expected: { dataset: { type: 'primary' } }, + sample: 'document.querySelector(\'[data-type="primary"]\')', + hints: ['Use square brackets for attribute selectors', 'Quote the attribute value'], + tags: ['dom', 'querySelector', 'attributes'], + }, + { + id: 'js-dom-006', + category: 'DOM Manipulation', + difficulty: 'medium', + title: 'Descendant Selector', + text: 'Select all list items inside a nav element.', + setup: 'const document = { querySelectorAll: (sel) => sel === "nav li" ? [{ tagName: "LI" }, { tagName: "LI" }, { tagName: "LI" }] : [] };', + setupCode: 'const document = { querySelectorAll: (sel) => sel === "nav li" ? [{ tagName: "LI" }, { tagName: "LI" }, { tagName: "LI" }] : [] };', + expected: [{ tagName: 'LI' }, { tagName: 'LI' }, { tagName: 'LI' }], + sample: 'document.querySelectorAll("nav li")', + hints: ['Space between selectors means descendant', 'This selects all li inside nav'], + tags: ['dom', 'querySelectorAll', 'combinators'], + }, + { + id: 'js-dom-007', + category: 'DOM Manipulation', + difficulty: 'medium', + title: 'Direct Child Selector', + text: 'Select only direct child paragraphs of an article element.', + setup: 'const document = { querySelectorAll: (sel) => sel === "article > p" ? [{ tagName: "P", direct: true }] : [] };', + setupCode: 'const document = { querySelectorAll: (sel) => sel === "article > p" ? [{ tagName: "P", direct: true }] : [] };', + expected: [{ tagName: 'P', direct: true }], + sample: 'document.querySelectorAll("article > p")', + hints: ['Use > for direct child combinator', 'This excludes nested paragraphs'], + tags: ['dom', 'querySelectorAll', 'combinators'], + }, + { + id: 'js-dom-008', + category: 'DOM Manipulation', + difficulty: 'medium', + title: 'Multiple Selectors', + text: 'Select all h1, h2, and h3 elements at once.', + setup: 'const document = { querySelectorAll: (sel) => sel === "h1, h2, h3" ? [{ tagName: "H1" }, { tagName: "H2" }, { tagName: "H3" }] : [] };', + setupCode: 'const document = { querySelectorAll: (sel) => sel === "h1, h2, h3" ? [{ tagName: "H1" }, { tagName: "H2" }, { tagName: "H3" }] : [] };', + expected: [{ tagName: 'H1' }, { tagName: 'H2' }, { tagName: 'H3' }], + sample: 'document.querySelectorAll("h1, h2, h3")', + hints: ['Separate multiple selectors with commas', 'Returns all matching elements'], + tags: ['dom', 'querySelectorAll', 'selectors'], + }, + { + id: 'js-dom-009', + category: 'DOM Manipulation', + difficulty: 'medium', + title: 'Pseudo-class Selector', + text: 'Select the first child of a list.', + setup: 'const document = { querySelector: (sel) => sel === "ul li:first-child" ? { textContent: "First Item" } : null };', + setupCode: 'const document = { querySelector: (sel) => sel === "ul li:first-child" ? { textContent: "First Item" } : null };', + expected: { textContent: 'First Item' }, + sample: 'document.querySelector("ul li:first-child")', + hints: ['Use :first-child pseudo-class', 'This selects the first li in ul'], + tags: ['dom', 'querySelector', 'pseudo-classes'], + }, + { + id: 'js-dom-010', + category: 'DOM Manipulation', + difficulty: 'hard', + title: 'Complex Selector Chain', + text: 'Select checked checkboxes inside a form with id "settings".', + setup: 'const document = { querySelectorAll: (sel) => sel === "#settings input[type=checkbox]:checked" ? [{ checked: true }, { checked: true }] : [] };', + setupCode: 'const document = { querySelectorAll: (sel) => sel === "#settings input[type=checkbox]:checked" ? [{ checked: true }, { checked: true }] : [] };', + expected: [{ checked: true }, { checked: true }], + sample: 'document.querySelectorAll("#settings input[type=checkbox]:checked")', + hints: ['Combine ID, attribute, and pseudo-class selectors', 'Use :checked for selected checkboxes'], + tags: ['dom', 'querySelectorAll', 'complex-selectors'], + }, + + // ======================================== + // DOM MANIPULATION - createElement/appendChild + // ======================================== + { + id: 'js-dom-011', + category: 'DOM Manipulation', + difficulty: 'easy', + title: 'Create a Div Element', + text: 'Create a new div element using document.createElement.', + setup: 'const document = { createElement: (tag) => ({ tagName: tag.toUpperCase(), children: [] }) };', + setupCode: 'const document = { createElement: (tag) => ({ tagName: tag.toUpperCase(), children: [] }) };', + expected: { tagName: 'DIV', children: [] }, + sample: 'document.createElement("div")', + hints: ['Pass the tag name as a string', 'Tag name is case-insensitive'], + tags: ['dom', 'createElement', 'elements'], + }, + { + id: 'js-dom-012', + category: 'DOM Manipulation', + difficulty: 'easy', + title: 'Create and Set Text Content', + text: 'Create a paragraph element and set its text content to "Hello World".', + setup: 'const document = { createElement: (tag) => ({ tagName: tag.toUpperCase(), textContent: "" }) };', + setupCode: 'const document = { createElement: (tag) => ({ tagName: tag.toUpperCase(), textContent: "" }) };', + expected: { tagName: 'P', textContent: 'Hello World' }, + sample: 'const p = document.createElement("p"); p.textContent = "Hello World"; p', + hints: ['First create the element', 'Then set the textContent property'], + tags: ['dom', 'createElement', 'textContent'], + }, + { + id: 'js-dom-013', + category: 'DOM Manipulation', + difficulty: 'easy', + title: 'Append Child Element', + text: 'Create a span and append it to the parent div. Return the parent.', + setup: 'const parent = { tagName: "DIV", children: [], appendChild(child) { this.children.push(child); return child; } }; const document = { createElement: (tag) => ({ tagName: tag.toUpperCase() }) };', + setupCode: 'const parent = { tagName: "DIV", children: [], appendChild(child) { this.children.push(child); return child; } }; const document = { createElement: (tag) => ({ tagName: tag.toUpperCase() }) };', + expected: { tagName: 'DIV', children: [{ tagName: 'SPAN' }] }, + sample: 'parent.appendChild(document.createElement("span")); parent', + hints: ['Use appendChild to add a child', 'appendChild modifies the parent in place'], + tags: ['dom', 'appendChild', 'elements'], + }, + { + id: 'js-dom-014', + category: 'DOM Manipulation', + difficulty: 'easy', + title: 'Create Button with Text', + text: 'Create a button element with text "Click Me".', + setup: 'const document = { createElement: (tag) => ({ tagName: tag.toUpperCase(), textContent: "" }) };', + setupCode: 'const document = { createElement: (tag) => ({ tagName: tag.toUpperCase(), textContent: "" }) };', + expected: { tagName: 'BUTTON', textContent: 'Click Me' }, + sample: 'const btn = document.createElement("button"); btn.textContent = "Click Me"; btn', + hints: ['Create button element first', 'Set textContent for the button text'], + tags: ['dom', 'createElement', 'button'], + }, + { + id: 'js-dom-015', + category: 'DOM Manipulation', + difficulty: 'medium', + title: 'Create Element with Attribute', + text: 'Create an anchor element with href attribute set to "https://example.com".', + setup: 'const document = { createElement: (tag) => ({ tagName: tag.toUpperCase(), setAttribute(name, value) { this[name] = value; } }) };', + setupCode: 'const document = { createElement: (tag) => ({ tagName: tag.toUpperCase(), setAttribute(name, value) { this[name] = value; } }) };', + expected: { tagName: 'A', href: 'https://example.com' }, + sample: 'const a = document.createElement("a"); a.setAttribute("href", "https://example.com"); a', + hints: ['Use setAttribute to add attributes', 'First argument is attribute name, second is value'], + tags: ['dom', 'createElement', 'setAttribute'], + }, + { + id: 'js-dom-016', + category: 'DOM Manipulation', + difficulty: 'medium', + title: 'Create Nested Elements', + text: 'Create a ul with one li child containing text "Item 1". Return the ul.', + setup: 'const document = { createElement: (tag) => ({ tagName: tag.toUpperCase(), textContent: "", children: [], appendChild(child) { this.children.push(child); return child; } }) };', + setupCode: 'const document = { createElement: (tag) => ({ tagName: tag.toUpperCase(), textContent: "", children: [], appendChild(child) { this.children.push(child); return child; } }) };', + expected: { tagName: 'UL', textContent: '', children: [{ tagName: 'LI', textContent: 'Item 1', children: [] }] }, + sample: 'const ul = document.createElement("ul"); const li = document.createElement("li"); li.textContent = "Item 1"; ul.appendChild(li); ul', + hints: ['Create both elements separately', 'Set text on li before appending'], + tags: ['dom', 'createElement', 'appendChild', 'nested'], + }, + { + id: 'js-dom-017', + category: 'DOM Manipulation', + difficulty: 'medium', + title: 'Insert Before Element', + text: 'Insert a new element before an existing one. Return the parent.', + setup: 'const existing = { id: "existing" }; const parent = { children: [existing], insertBefore(newEl, ref) { const idx = this.children.indexOf(ref); this.children.splice(idx, 0, newEl); return newEl; } }; const newEl = { id: "new" };', + setupCode: 'const existing = { id: "existing" }; const parent = { children: [existing], insertBefore(newEl, ref) { const idx = this.children.indexOf(ref); this.children.splice(idx, 0, newEl); return newEl; } }; const newEl = { id: "new" };', + expected: { children: [{ id: 'new' }, { id: 'existing' }] }, + sample: 'parent.insertBefore(newEl, existing); parent', + hints: ['insertBefore takes new element and reference element', 'New element is inserted before the reference'], + tags: ['dom', 'insertBefore', 'manipulation'], + }, + { + id: 'js-dom-018', + category: 'DOM Manipulation', + difficulty: 'medium', + title: 'Remove Child Element', + text: 'Remove the child element from the parent and return the removed element.', + setup: 'const child = { id: "child" }; const parent = { children: [child], removeChild(el) { const idx = this.children.indexOf(el); this.children.splice(idx, 1); return el; } };', + setupCode: 'const child = { id: "child" }; const parent = { children: [child], removeChild(el) { const idx = this.children.indexOf(el); this.children.splice(idx, 1); return el; } };', + expected: { id: 'child' }, + sample: 'parent.removeChild(child)', + hints: ['removeChild returns the removed element', 'Pass the element to remove as argument'], + tags: ['dom', 'removeChild', 'manipulation'], + }, + { + id: 'js-dom-019', + category: 'DOM Manipulation', + difficulty: 'hard', + title: 'Replace Child Element', + text: 'Replace the old element with a new one. Return the parent.', + setup: 'const oldEl = { id: "old" }; const newEl = { id: "new" }; const parent = { children: [oldEl], replaceChild(newChild, oldChild) { const idx = this.children.indexOf(oldChild); this.children[idx] = newChild; return oldChild; } };', + setupCode: 'const oldEl = { id: "old" }; const newEl = { id: "new" }; const parent = { children: [oldEl], replaceChild(newChild, oldChild) { const idx = this.children.indexOf(oldChild); this.children[idx] = newChild; return oldChild; } };', + expected: { children: [{ id: 'new' }] }, + sample: 'parent.replaceChild(newEl, oldEl); parent', + hints: ['replaceChild takes new element first, then old', 'Order of arguments matters'], + tags: ['dom', 'replaceChild', 'manipulation'], + }, + { + id: 'js-dom-020', + category: 'DOM Manipulation', + difficulty: 'hard', + title: 'Clone Element Deep', + text: 'Clone the element including all its children.', + setup: 'const el = { id: "original", children: [{ id: "child" }], cloneNode(deep) { return deep ? { id: this.id, children: [...this.children], cloned: true } : { id: this.id, children: [], cloned: true }; } };', + setupCode: 'const el = { id: "original", children: [{ id: "child" }], cloneNode(deep) { return deep ? { id: this.id, children: [...this.children], cloned: true } : { id: this.id, children: [], cloned: true }; } };', + expected: { id: 'original', children: [{ id: 'child' }], cloned: true }, + sample: 'el.cloneNode(true)', + hints: ['Pass true for deep clone', 'Deep clone includes all descendants'], + tags: ['dom', 'cloneNode', 'manipulation'], + }, + + // ======================================== + // DOM MANIPULATION - innerHTML/textContent + // ======================================== + { + id: 'js-dom-021', + category: 'DOM Manipulation', + difficulty: 'easy', + title: 'Set innerHTML', + text: 'Set the innerHTML of the element to contain a strong tag with "Bold Text".', + setup: 'const el = { innerHTML: "" };', + setupCode: 'const el = { innerHTML: "" };', + expected: { innerHTML: 'Bold Text' }, + sample: 'el.innerHTML = "Bold Text"; el', + hints: ['innerHTML accepts HTML string', 'Include the full HTML tags'], + tags: ['dom', 'innerHTML', 'html'], + }, + { + id: 'js-dom-022', + category: 'DOM Manipulation', + difficulty: 'easy', + title: 'Get Text Content', + text: 'Extract the text content from the element.', + setup: 'const el = { textContent: "Hello World", innerHTML: "Hello World" };', + setupCode: 'const el = { textContent: "Hello World", innerHTML: "Hello World" };', + expected: 'Hello World', + sample: 'el.textContent', + hints: ['textContent returns plain text', 'HTML tags are not included'], + tags: ['dom', 'textContent', 'text'], + }, + { + id: 'js-dom-023', + category: 'DOM Manipulation', + difficulty: 'easy', + title: 'Clear Element Content', + text: 'Clear all content from the element using innerHTML.', + setup: 'const el = { innerHTML: "Some content
More" };', + setupCode: 'const el = { innerHTML: "Some content
More" };', + expected: { innerHTML: '' }, + sample: 'el.innerHTML = ""; el', + hints: ['Set innerHTML to empty string', 'This removes all child elements'], + tags: ['dom', 'innerHTML', 'clear'], + }, + { + id: 'js-dom-024', + category: 'DOM Manipulation', + difficulty: 'medium', + title: 'Insert HTML with Template', + text: 'Create an HTML string for a list with items "One", "Two", "Three".', + setup: 'const items = ["One", "Two", "Three"];', + setupCode: 'const items = ["One", "Two", "Three"];', + expected: 'Content
Content
Content