React Coding Practice
Question Bank with Test Cases
LeetCode / HackerRank Style
Easy: 10 Medium: 10 Hard: 8
EASY QUESTIONS
EASY
Q1. Flatten Nested Array
Given a nested array arr, return a flattened array. A flattened array contains all the elements of the
original array but with all nested arrays recursively expanded into a single-level array.
Note: Solve without using [Link].
Examples
Example 1
Input: arr = [1,[2],[3,4]]
Output: [1,2,3,4]
Note: All nested arrays were flattened into a single array.
Example 2
Input: arr = [1,[2,[3,[4]]],5]
Output: [1,2,3,4,5]
Note: The nested arrays were recursively flattened.
Example 3
Input: arr = []
Output: []
Note: There are no elements in the array.
Sample Test Cases
Test Cases at a Glance
1. flattens a single level of nesting
2. flattens deeply nested arrays
3. returns empty array for empty input
4. returns unchanged array when no nesting
[Link]
import { describe, expect, it } from "vitest";
import { flattenArray } from "./solution";
describe("Q1 - Flatten Nested Array (sample)", () => {
it("flattens a single level of nesting", () => {
expect(flattenArray([1, [2], [3, 4]])).toEqual([1, 2, 3, 4]);
});
it("flattens deeply nested arrays", () => {
expect(flattenArray([1, [2, [3, [4]]], 5])).toEqual([1, 2, 3, 4, 5]);
});
it("returns empty array for empty input", () => {
expect(flattenArray([])).toEqual([]);
});
it("returns unchanged array when no nesting", () => {
expect(flattenArray([1, 2, 3])).toEqual([1, 2, 3]);
});
});
Hidden Test Cases
Test Cases at a Glance
1. handles array with only nested arrays
2. handles 5 levels deep nesting
3. handles mixed types at various depths
4. does not use [Link] (structural check)
5. handles large deeply nested input without stack overflow
[Link]
import { describe, expect, it } from "vitest";
import { flattenArray } from "./solution";
describe("Q1 - Flatten Nested Array (hidden)", () => {
it("handles array with only nested arrays", () => {
expect(flattenArray([[1], [2], [3]])).toEqual([1, 2, 3]);
});
it("handles 5 levels deep nesting", () => {
expect(flattenArray([[[[[42]]]]])).toEqual([42]);
});
it("handles mixed types at various depths", () => {
expect(flattenArray([1, [2, [3]], "a", ["b", ["c"]]])).toEqual([1, 2, 3, "a",
"b", "c"]);
});
it("does not use [Link] (structural check)", () => {
const originalFlat = [Link];
let flatCalled = false;
[Link] = function(...args) { flatCalled = true; return
[Link](this, args); };
flattenArray([1, [2]]);
[Link] = originalFlat;
expect(flatCalled).toBe(false);
});
it("handles large deeply nested input without stack overflow", () => {
let arr: any = [1];
for (let i = 0; i < 100; i++) arr = [arr];
expect(flattenArray(arr)).toEqual([1]);
});
});
EASY
Q2. Group Array by Size
Given an array arr and an integer size, return an array of groups. Each group should contain size
consecutive elements from arr. The final group may contain fewer than size elements.
Note: Solve without using external utility libraries.
Examples
Example 1
Input: arr = [1,2,3,4,5,6], size = 2
Output: [[1,2],[3,4],[5,6]]
Example 2
Input: arr = [1,2,3,4,5], size = 4
Output: [[1,2,3,4],[5]]
Note: Last group has fewer elements.
Example 3
Input: arr = [], size = 3
Output: []
Sample Test Cases
Test Cases at a Glance
1. groups evenly divisible array
2. handles remainder in final group
3. returns empty array for empty input
[Link]
import { describe, expect, it } from "vitest";
import { groupBySize } from "./solution";
describe("Q2 - Group Array by Size (sample)", () => {
it("groups evenly divisible array", () => {
expect(groupBySize([1, 2, 3, 4, 5, 6], 2)).toEqual([[1, 2], [3, 4], [5, 6]]);
});
it("handles remainder in final group", () => {
expect(groupBySize([1, 2, 3, 4, 5], 4)).toEqual([[1, 2, 3, 4], [5]]);
});
it("returns empty array for empty input", () => {
expect(groupBySize([], 3)).toEqual([]);
});
});
Hidden Test Cases
Test Cases at a Glance
1. size equals array length returns one group
2. size of 1 returns each element in own group
3. size larger than array returns single group
4. works with string array
5. groups array of 7 with size 3 correctly
[Link]
import { describe, expect, it } from "vitest";
import { groupBySize } from "./solution";
describe("Q2 - Group Array by Size (hidden)", () => {
it("size equals array length returns one group", () => {
expect(groupBySize([1, 2, 3], 3)).toEqual([[1, 2, 3]]);
});
it("size of 1 returns each element in own group", () => {
expect(groupBySize([5, 10, 15], 1)).toEqual([[5], [10], [15]]);
});
it("size larger than array returns single group", () => {
expect(groupBySize([1, 2], 10)).toEqual([[1, 2]]);
});
it("works with string array", () => {
expect(groupBySize(["a","b","c","d"], 2)).toEqual([["a","b"], ["c","d"]]);
});
it("groups array of 7 with size 3 correctly", () => {
expect(groupBySize([1,2,3,4,5,6,7], 3)).toEqual([[1,2,3],[4,5,6],[7]]);
});
});
EASY
Q3. Reverse Array in Groups
Given an array arr and an integer size, reverse the elements of the array in groups of length size. The
final group should also be reversed even if it contains fewer than size elements.
Examples
Example 1
Input: arr = [1,2,3,4,5,6], size = 2
Output: [2,1,4,3,6,5]
Example 2
Input: arr = [1,2,3,4,5], size = 3
Output: [3,2,1,5,4]
Example 3
Input: arr = [8,9], size = 5
Output: [9,8]
Sample Test Cases
Test Cases at a Glance
1. reverses evenly divisible groups
2. reverses with remainder final group
3. reverses when size exceeds array length
[Link]
import { describe, expect, it } from "vitest";
import { reverseInGroups } from "./solution";
describe("Q3 - Reverse Array in Groups (sample)", () => {
it("reverses evenly divisible groups", () => {
expect(reverseInGroups([1,2,3,4,5,6], 2)).toEqual([2,1,4,3,6,5]);
});
it("reverses with remainder final group", () => {
expect(reverseInGroups([1,2,3,4,5], 3)).toEqual([3,2,1,5,4]);
});
it("reverses when size exceeds array length", () => {
expect(reverseInGroups([8,9], 5)).toEqual([9,8]);
});
});
Hidden Test Cases
Test Cases at a Glance
1. size 1 returns original array
2. size equals length reverses entire array
3. empty array returns empty
4. single element array returns unchanged
5. handles group size 3 with 7 elements correctly
[Link]
import { describe, expect, it } from "vitest";
import { reverseInGroups } from "./solution";
describe("Q3 - Reverse Array in Groups (hidden)", () => {
it("size 1 returns original array", () => {
expect(reverseInGroups([1, 2, 3], 1)).toEqual([1, 2, 3]);
});
it("size equals length reverses entire array", () => {
expect(reverseInGroups([1, 2, 3, 4], 4)).toEqual([4, 3, 2, 1]);
});
it("empty array returns empty", () => {
expect(reverseInGroups([], 3)).toEqual([]);
});
it("single element array returns unchanged", () => {
expect(reverseInGroups([42], 3)).toEqual([42]);
});
it("handles group size 3 with 7 elements correctly", () => {
expect(reverseInGroups([1,2,3,4,5,6,7], 3)).toEqual([3,2,1,6,5,4,7]);
});
});
EASY
Q4. Split String into Chunks
Given a string s and an integer size, split the string into chunks of length size. Return an array
containing the chunks in order. The last chunk may contain fewer than size characters.
Examples
Example 1
Input: s = "abcdef", size = 2
Output: ["ab","cd","ef"]
Example 2
Input: s = "helloworld", size = 3
Output: ["hel","low","orl","d"]
Example 3
Input: s = "", size = 1
Output: []
Sample Test Cases
Test Cases at a Glance
1. splits evenly into chunks
2. last chunk has fewer characters
3. returns empty array for empty string
[Link]
import { describe, expect, it } from "vitest";
import { splitIntoChunks } from "./solution";
describe("Q4 - Split String into Chunks (sample)", () => {
it("splits evenly into chunks", () => {
expect(splitIntoChunks("abcdef", 2)).toEqual(["ab", "cd", "ef"]);
});
it("last chunk has fewer characters", () => {
expect(splitIntoChunks("helloworld", 3)).toEqual(["hel", "low", "orl", "d"]);
});
it("returns empty array for empty string", () => {
expect(splitIntoChunks("", 1)).toEqual([]);
});
});
Hidden Test Cases
Test Cases at a Glance
1. size equals string length returns one chunk
2. size 1 splits every character
3. size larger than string returns single chunk
4. handles unicode characters correctly
5. handles size of exactly 4 on 9-char string
[Link]
import { describe, expect, it } from "vitest";
import { splitIntoChunks } from "./solution";
describe("Q4 - Split String into Chunks (hidden)", () => {
it("size equals string length returns one chunk", () => {
expect(splitIntoChunks("hello", 5)).toEqual(["hello"]);
});
it("size 1 splits every character", () => {
expect(splitIntoChunks("abc", 1)).toEqual(["a", "b", "c"]);
});
it("size larger than string returns single chunk", () => {
expect(splitIntoChunks("hi", 10)).toEqual(["hi"]);
});
it("handles unicode characters correctly", () => {
expect(splitIntoChunks("aabbcc", 2)).toEqual(["aa", "bb", "cc"]);
});
it("handles size of exactly 4 on 9-char string", () => {
expect(splitIntoChunks("123456789", 4)).toEqual(["1234", "5678", "9"]);
});
});
EASY
Q5. Merge Consecutive Subarrays
Given a 2D array arr, return a single merged array containing all elements in order.
Note: Solve without using [Link].
Examples
Example 1
Input: arr = [[1,2],[3,4],[5]]
Output: [1,2,3,4,5]
Example 2
Input: arr = [[7],[8,9],[]]
Output: [7,8,9]
Example 3
Input: arr = []
Output: []
Sample Test Cases
Test Cases at a Glance
1. merges standard 2D array
2. handles empty subarray
3. returns empty for empty outer array
[Link]
import { describe, expect, it } from "vitest";
import { mergeSubarrays } from "./solution";
describe("Q5 - Merge Consecutive Subarrays (sample)", () => {
it("merges standard 2D array", () => {
expect(mergeSubarrays([[1,2],[3,4],[5]])).toEqual([1,2,3,4,5]);
});
it("handles empty subarray", () => {
expect(mergeSubarrays([[7],[8,9],[]])).toEqual([7,8,9]);
});
it("returns empty for empty outer array", () => {
expect(mergeSubarrays([])).toEqual([]);
});
});
Hidden Test Cases
Test Cases at a Glance
1. all empty subarrays returns empty
2. single subarray returns its elements
3. preserves order of elements across subarrays
4. does not use [Link]
[Link]
import { describe, expect, it } from "vitest";
import { mergeSubarrays } from "./solution";
describe("Q5 - Merge Consecutive Subarrays (hidden)", () => {
it("all empty subarrays returns empty", () => {
expect(mergeSubarrays([[], [], []])).toEqual([]);
});
it("single subarray returns its elements", () => {
expect(mergeSubarrays([[1, 2, 3]])).toEqual([1, 2, 3]);
});
it("preserves order of elements across subarrays", () => {
expect(mergeSubarrays([[3],[1],[2]])).toEqual([3,1,2]);
});
it("does not use [Link]", () => {
const orig = [Link];
let called = false;
[Link] = function(...a) { called = true; return [Link](this,
a); };
mergeSubarrays([[1],[2]]);
[Link] = orig;
expect(called).toBe(false);
});
});
EASY
Q6. Rotate Array by K Positions
Given an array arr and an integer k, rotate the array to the right by k positions.
Examples
Example 1
Input: arr = [1,2,3,4,5], k = 2
Output: [4,5,1,2,3]
Example 2
Input: arr = [1,2], k = 3
Output: [2,1]
Note: Rotating 3 times is equivalent to rotating 1 time.
Example 3
Input: arr = [], k = 1
Output: []
Sample Test Cases
Test Cases at a Glance
1. rotates right by k=2
2. handles k larger than array length
3. returns empty array for empty input
[Link]
import { describe, expect, it } from "vitest";
import { rotateArray } from "./solution";
describe("Q6 - Rotate Array by K Positions (sample)", () => {
it("rotates right by k=2", () => {
expect(rotateArray([1,2,3,4,5], 2)).toEqual([4,5,1,2,3]);
});
it("handles k larger than array length", () => {
expect(rotateArray([1,2], 3)).toEqual([2,1]);
});
it("returns empty array for empty input", () => {
expect(rotateArray([], 1)).toEqual([]);
});
});
Hidden Test Cases
Test Cases at a Glance
1. k=0 returns original array
2. k equals array length returns original
3. single element array always returns itself
4. handles k that is a multiple of array length
5. rotates by 1 correctly
[Link]
import { describe, expect, it } from "vitest";
import { rotateArray } from "./solution";
describe("Q6 - Rotate Array by K Positions (hidden)", () => {
it("k=0 returns original array", () => {
expect(rotateArray([1,2,3], 0)).toEqual([1,2,3]);
});
it("k equals array length returns original", () => {
expect(rotateArray([1,2,3], 3)).toEqual([1,2,3]);
});
it("single element array always returns itself", () => {
expect(rotateArray([99], 100)).toEqual([99]);
});
it("handles k that is a multiple of array length", () => {
expect(rotateArray([1,2,3,4], 8)).toEqual([1,2,3,4]);
});
it("rotates by 1 correctly", () => {
expect(rotateArray([1,2,3,4,5], 1)).toEqual([5,1,2,3,4]);
});
});
EASY
Q7. Partition Array into Equal Pairs
Given an array arr, return an array containing pairs of consecutive elements. If the array has an odd
number of elements, the final pair should contain only one element.
Examples
Example 1
Input: arr = [1,2,3,4]
Output: [[1,2],[3,4]]
Example 2
Input: arr = [5,6,7]
Output: [[5,6],[7]]
Example 3
Input: arr = []
Output: []
Sample Test Cases
Test Cases at a Glance
1. pairs even-length array
2. handles odd-length array with single-element tail
3. returns empty for empty input
[Link]
import { describe, expect, it } from "vitest";
import { partitionIntoPairs } from "./solution";
describe("Q7 - Partition Array into Equal Pairs (sample)", () => {
it("pairs even-length array", () => {
expect(partitionIntoPairs([1,2,3,4])).toEqual([[1,2],[3,4]]);
});
it("handles odd-length array with single-element tail", () => {
expect(partitionIntoPairs([5,6,7])).toEqual([[5,6],[7]]);
});
it("returns empty for empty input", () => {
expect(partitionIntoPairs([])).toEqual([]);
});
});
Hidden Test Cases
Test Cases at a Glance
1. single element yields one pair with one element
2. two elements yields one pair
3. six elements yields three pairs
4. works with string elements
[Link]
import { describe, expect, it } from "vitest";
import { partitionIntoPairs } from "./solution";
describe("Q7 - Partition Array into Equal Pairs (hidden)", () => {
it("single element yields one pair with one element", () => {
expect(partitionIntoPairs([42])).toEqual([[42]]);
});
it("two elements yields one pair", () => {
expect(partitionIntoPairs([1,2])).toEqual([[1,2]]);
});
it("six elements yields three pairs", () => {
expect(partitionIntoPairs([1,2,3,4,5,6])).toEqual([[1,2],[3,4],[5,6]]);
});
it("works with string elements", () => {
expect(partitionIntoPairs(["a","b","c"])).toEqual([["a","b"],["c"]]);
});
});
EASY
Q8. Remove Every Nth Element
Given an array arr and an integer n, remove every nth element from the array (1-based position).
Return the remaining elements in order.
Examples
Example 1
Input: arr = [1,2,3,4,5,6], n = 2
Output: [1,3,5]
Note: Elements at positions 2, 4, 6 removed.
Example 2
Input: arr = [10,20,30,40,50], n = 3
Output: [10,20,40,50]
Note: Element at position 3 removed.
Example 3
Input: arr = [7,8], n = 1
Output: []
Note: Every element is removed.
Sample Test Cases
Test Cases at a Glance
1. removes every 2nd element
2. removes every 3rd element
3. n=1 removes all elements
[Link]
import { describe, expect, it } from "vitest";
import { removeEveryNth } from "./solution";
describe("Q8 - Remove Every Nth Element (sample)", () => {
it("removes every 2nd element", () => {
expect(removeEveryNth([1,2,3,4,5,6], 2)).toEqual([1,3,5]);
});
it("removes every 3rd element", () => {
expect(removeEveryNth([10,20,30,40,50], 3)).toEqual([10,20,40,50]);
});
it("n=1 removes all elements", () => {
expect(removeEveryNth([7,8], 1)).toEqual([]);
});
});
Hidden Test Cases
Test Cases at a Glance
1. n larger than array length removes nothing
2. empty array returns empty
3. n equals array length removes last element
4. removes correct positions in 7-element array with n=3
[Link]
import { describe, expect, it } from "vitest";
import { removeEveryNth } from "./solution";
describe("Q8 - Remove Every Nth Element (hidden)", () => {
it("n larger than array length removes nothing", () => {
expect(removeEveryNth([1,2,3], 10)).toEqual([1,2,3]);
});
it("empty array returns empty", () => {
expect(removeEveryNth([], 3)).toEqual([]);
});
it("n equals array length removes last element", () => {
expect(removeEveryNth([1,2,3,4], 4)).toEqual([1,2,3]);
});
it("removes correct positions in 7-element array with n=3", () => {
expect(removeEveryNth([1,2,3,4,5,6,7], 3)).toEqual([1,2,4,5,7]);
});
});
EASY
Q9. Divide Array into Two Halves
Given an array arr, divide it into two halves. If the array length is odd, the first half should contain one
extra element. Return the two halves as a 2D array.
Examples
Example 1
Input: arr = [1,2,3,4]
Output: [[1,2],[3,4]]
Example 2
Input: arr = [1,2,3,4,5]
Output: [[1,2,3],[4,5]]
Note: Odd length: first half gets the extra element.
Example 3
Input: arr = []
Output: [[],[]]
Sample Test Cases
Test Cases at a Glance
1. splits even-length array equally
2. first half gets extra element on odd length
3. empty array returns two empty halves
[Link]
import { describe, expect, it } from "vitest";
import { divideIntoHalves } from "./solution";
describe("Q9 - Divide Array into Two Halves (sample)", () => {
it("splits even-length array equally", () => {
expect(divideIntoHalves([1,2,3,4])).toEqual([[1,2],[3,4]]);
});
it("first half gets extra element on odd length", () => {
expect(divideIntoHalves([1,2,3,4,5])).toEqual([[1,2,3],[4,5]]);
});
it("empty array returns two empty halves", () => {
expect(divideIntoHalves([])).toEqual([[],[]]);
});
});
Hidden Test Cases
Test Cases at a Glance
1. single element goes to first half
2. two elements split evenly
3. seven elements split 4 and 3
4. preserves original array (no mutation)
[Link]
import { describe, expect, it } from "vitest";
import { divideIntoHalves } from "./solution";
describe("Q9 - Divide Array into Two Halves (hidden)", () => {
it("single element goes to first half", () => {
expect(divideIntoHalves([42])).toEqual([[42],[]]);
});
it("two elements split evenly", () => {
expect(divideIntoHalves([1,2])).toEqual([[1],[2]]);
});
it("seven elements split 4 and 3", () => {
expect(divideIntoHalves([1,2,3,4,5,6,7])).toEqual([[1,2,3,4],[5,6,7]]);
});
it("preserves original array (no mutation)", () => {
const arr = [1,2,3,4];
divideIntoHalves(arr);
expect(arr).toEqual([1,2,3,4]);
});
});
EASY
Q10. Interleave Two Arrays
Given two arrays arr1 and arr2, return a new array by alternating elements from each array. If one array
is longer, append the remaining elements at the end.
Examples
Example 1
Input: arr1 = [1,3,5], arr2 = [2,4,6]
Output: [1,2,3,4,5,6]
Example 2
Input: arr1 = [1,2], arr2 = [3,4,5,6]
Output: [1,3,2,4,5,6]
Example 3
Input: arr1 = [], arr2 = [1,2]
Output: [1,2]
Sample Test Cases
Test Cases at a Glance
1. interleaves equal-length arrays
2. appends remaining elements from longer array
3. first array empty returns second unchanged
[Link]
import { describe, expect, it } from "vitest";
import { interleaveArrays } from "./solution";
describe("Q10 - Interleave Two Arrays (sample)", () => {
it("interleaves equal-length arrays", () => {
expect(interleaveArrays([1,3,5],[2,4,6])).toEqual([1,2,3,4,5,6]);
});
it("appends remaining elements from longer array", () => {
expect(interleaveArrays([1,2],[3,4,5,6])).toEqual([1,3,2,4,5,6]);
});
it("first array empty returns second unchanged", () => {
expect(interleaveArrays([],[1,2])).toEqual([1,2]);
});
});
Hidden Test Cases
Test Cases at a Glance
1. both arrays empty returns empty
2. second array empty returns first unchanged
3. arr1 longer appends remaining arr1 elements
4. single element arrays interleave correctly
[Link]
import { describe, expect, it } from "vitest";
import { interleaveArrays } from "./solution";
describe("Q10 - Interleave Two Arrays (hidden)", () => {
it("both arrays empty returns empty", () => {
expect(interleaveArrays([],[])).toEqual([]);
});
it("second array empty returns first unchanged", () => {
expect(interleaveArrays([1,2,3],[])).toEqual([1,2,3]);
});
it("arr1 longer appends remaining arr1 elements", () => {
expect(interleaveArrays([1,2,3,4],[5])).toEqual([1,5,2,3,4]);
});
it("single element arrays interleave correctly", () => {
expect(interleaveArrays([1],[2])).toEqual([1,2]);
});
});
MEDIUM QUESTIONS
MEDIUM
M1. Item List Manager
Create a React application called 'Item List Manager' that displays a list of items and allows users to
add new items to the list. Items are displayed in an unordered list (<ul>).
Requirements
- When the application loads, it should display an empty list.
- The input field should accept user text input.
- When the button is clicked, the text from the input field is added to the list and the input field is
cleared.
- If the input field is empty and the button is clicked, nothing should be added to the list.
Sample Test Cases
Test Cases at a Glance
1. renders with an empty list initially
2. adds an item when text is entered and button is clicked
3. clears the input field after adding an item
4. does not add empty item when input is blank
[Link]
import { describe, expect, it } from "vitest";
import { render, screen, fireEvent } from "@testing-library/react";
import ItemListManager from "./ItemListManager";
describe("M1 - Item List Manager (sample)", () => {
it("renders with an empty list initially", () => {
render(<ItemListManager />);
expect([Link]("listitem")).toHaveLength(0);
});
it("adds an item when text is entered and button is clicked", () => {
render(<ItemListManager />);
[Link]([Link]("textbox"), { target: { value: "First Item" }
});
[Link]([Link]("button", { name: /add item/i }));
expect([Link]("First Item")).toBeInTheDocument();
});
it("clears the input field after adding an item", () => {
render(<ItemListManager />);
const input = [Link]("textbox");
[Link](input, { target: { value: "Item" } });
[Link]([Link]("button", { name: /add item/i }));
expect(input).toHaveValue("");
});
it("does not add empty item when input is blank", () => {
render(<ItemListManager />);
[Link]([Link]("button", { name: /add item/i }));
expect([Link]("listitem")).toHaveLength(0);
});
});
Hidden Test Cases
Test Cases at a Glance
1. adds multiple items in order
2. does not add whitespace-only input
3. renders items inside a <ul> element
4. can add 10+ items without issue
[Link]
import { describe, expect, it } from "vitest";
import { render, screen, fireEvent } from "@testing-library/react";
import ItemListManager from "./ItemListManager";
describe("M1 - Item List Manager (hidden)", () => {
it("adds multiple items in order", () => {
render(<ItemListManager />);
const input = [Link]("textbox");
const btn = [Link]("button", { name: /add item/i });
[Link](input, { target: { value: "First" } });
[Link](btn);
[Link](input, { target: { value: "Second" } });
[Link](btn);
const items = [Link]("listitem");
expect(items).toHaveLength(2);
expect(items[0]).toHaveTextContent("First");
expect(items[1]).toHaveTextContent("Second");
});
it("does not add whitespace-only input", () => {
render(<ItemListManager />);
[Link]([Link]("textbox"), { target: { value: " " } });
[Link]([Link]("button", { name: /add item/i }));
expect([Link]("listitem")).toHaveLength(0);
});
it("renders items inside a <ul> element", () => {
const { container } = render(<ItemListManager />);
[Link]([Link]("textbox"), { target: { value: "Item" } });
[Link]([Link]("button", { name: /add item/i }));
expect([Link]("ul")).[Link]();
expect([Link]("li")).toHaveLength(1);
});
it("can add 10+ items without issue", () => {
render(<ItemListManager />);
const input = [Link]("textbox");
const btn = [Link]("button", { name: /add item/i });
for (let i = 1; i <= 10; i++) {
[Link](input, { target: { value: `Item ${i}` } });
[Link](btn);
}
expect([Link]("listitem")).toHaveLength(10);
});
});
MEDIUM
M2. Code Review Feedback
Create a React application called 'Code Review Feedback' that tracks upvote and downvote counts for
five code quality aspects: Readability, Performance, Security, Documentation, and Testing.
Requirements
- Display all five aspects with 'Upvote' and 'Downvote' buttons.
- Initial count for all aspects is 0.
- Clicking Upvote increments that aspect's upvote count; Downvote increments the downvote count.
- Counts update in the UI immediately.
- A subtle animation should appear when a count is updated.
Sample Test Cases
Test Cases at a Glance
1. renders all five aspects
2. initial counts are all 0
3. increments upvote count for Readability
4. increments downvote count independently
[Link]
import { describe, expect, it } from "vitest";
import { render, screen, fireEvent } from "@testing-library/react";
import CodeReviewFeedback from "./CodeReviewFeedback";
describe("M2 - Code Review Feedback (sample)", () => {
it("renders all five aspects", () => {
render(<CodeReviewFeedback />);
["Readability","Performance","Security","Documentation","Testing"].forEach(a =>
{
expect([Link](new RegExp(a, "i"))).toBeInTheDocument();
});
});
it("initial counts are all 0", () => {
render(<CodeReviewFeedback />);
const zeros = [Link]("0");
expect([Link]).toBeGreaterThanOrEqual(10); // 5 upvote + 5 downvote
});
it("increments upvote count for Readability", () => {
render(<CodeReviewFeedback />);
const upvoteBtns = [Link]("button", { name: /upvote/i });
[Link](upvoteBtns[0]);
expect([Link]("1").length).toBeGreaterThanOrEqual(1);
});
it("increments downvote count independently", () => {
render(<CodeReviewFeedback />);
const downvoteBtns = [Link]("button", { name: /downvote/i });
[Link](downvoteBtns[1]); // Performance downvote
expect([Link]("1").length).toBeGreaterThanOrEqual(1);
});
});
Hidden Test Cases
Test Cases at a Glance
1. upvote and downvote counts update independently per aspect
2. clicking upvote 5 times shows count of 5
3. each aspect has exactly one Upvote and one Downvote button
4. counts do not affect sibling aspects
[Link]
import { describe, expect, it } from "vitest";
import { render, screen, fireEvent } from "@testing-library/react";
import CodeReviewFeedback from "./CodeReviewFeedback";
describe("M2 - Code Review Feedback (hidden)", () => {
it("upvote and downvote counts update independently per aspect", () => {
render(<CodeReviewFeedback />);
const upBtns = [Link]("button", { name: /upvote/i });
const downBtns = [Link]("button", { name: /downvote/i });
[Link](upBtns[2]); // Security upvote
[Link](upBtns[2]);
[Link](downBtns[2]); // Security downvote
// Security upvote should be 2, downvote should be 1
expect([Link]("2").length).toBeGreaterThanOrEqual(1);
expect([Link]("1").length).toBeGreaterThanOrEqual(1);
});
it("clicking upvote 5 times shows count of 5", () => {
render(<CodeReviewFeedback />);
const upBtns = [Link]("button", { name: /upvote/i });
for (let i = 0; i < 5; i++) [Link](upBtns[0]);
expect([Link]("5").length).toBeGreaterThanOrEqual(1);
});
it("each aspect has exactly one Upvote and one Downvote button", () => {
render(<CodeReviewFeedback />);
expect([Link]("button", { name: /upvote/i })).toHaveLength(5);
expect([Link]("button", { name: /downvote/i })).toHaveLength(5);
});
it("counts do not affect sibling aspects", () => {
render(<CodeReviewFeedback />);
const upBtns = [Link]("button", { name: /upvote/i });
[Link](upBtns[4]); // Testing upvote only
// Only 1 count of "1" should exist
expect([Link]("1").length).toBe(1);
});
});
MEDIUM
M3. Contact Form
Create a React application called 'Contact Form' that collects Name, Email, and Message, validates
them, and displays submitted data below the form.
Requirements
- Fields: Name (text input), Email (text input), Message (textarea).
- Validate that no fields are empty on submit; show 'All fields are required.' error if any are.
- On successful submission, display submitted data below the form and clear all fields.
Sample Test Cases
Test Cases at a Glance
1. renders all form fields and submit button
2. shows error when any field is empty
3. displays submitted data below form on valid submission
[Link]
import { describe, expect, it } from "vitest";
import { render, screen, fireEvent } from "@testing-library/react";
import ContactForm from "./ContactForm";
describe("M3 - Contact Form (sample)", () => {
it("renders all form fields and submit button", () => {
render(<ContactForm />);
expect([Link](/name/i)).toBeInTheDocument();
expect([Link](/email/i)).toBeInTheDocument();
expect([Link](/message/i)).toBeInTheDocument();
expect([Link]("button", { name: /submit/i })).toBeInTheDocument();
});
it("shows error when any field is empty", () => {
render(<ContactForm />);
[Link]([Link]("button", { name: /submit/i }));
expect([Link]("All fields are required.")).toBeInTheDocument();
});
it("displays submitted data below form on valid submission", () => {
render(<ContactForm />);
[Link]([Link](/name/i), { target: { value: "John Doe"
} });
[Link]([Link](/email/i), { target: { value:
"john@[Link]" } });
[Link]([Link](/message/i), { target: { value: "Hello
there!" } });
[Link]([Link]("button", { name: /submit/i }));
expect([Link]("John Doe")).toBeInTheDocument();
expect([Link]("john@[Link]")).toBeInTheDocument();
});
});
Hidden Test Cases
Test Cases at a Glance
1. clears all fields after successful submission
2. does not show submitted data if validation fails
3. error message disappears after successful submission
[Link]
import { describe, expect, it } from "vitest";
import { render, screen, fireEvent } from "@testing-library/react";
import ContactForm from "./ContactForm";
describe("M3 - Contact Form (hidden)", () => {
it("clears all fields after successful submission", () => {
render(<ContactForm />);
[Link]([Link](/name/i), { target: { value: "Jane" }
});
[Link]([Link](/email/i), { target: { value:
"jane@[Link]" } });
[Link]([Link](/message/i), { target: { value: "Hi" }
});
[Link]([Link]("button", { name: /submit/i }));
expect([Link](/name/i)).toHaveValue("");
expect([Link](/email/i)).toHaveValue("");
expect([Link](/message/i)).toHaveValue("");
});
it("does not show submitted data if validation fails", () => {
render(<ContactForm />);
[Link]([Link](/name/i), { target: { value: "John" }
});
// email and message left empty
[Link]([Link]("button", { name: /submit/i }));
expect([Link]("John")).[Link]();
});
it("error message disappears after successful submission", () => {
render(<ContactForm />);
[Link]([Link]("button", { name: /submit/i }));
expect([Link]("All fields are required.")).toBeInTheDocument();
[Link]([Link](/name/i), { target: { value: "Sam" } });
[Link]([Link](/email/i), { target: { value: "s@[Link]"
} });
[Link]([Link](/message/i), { target: { value: "Hi" }
});
[Link]([Link]("button", { name: /submit/i }));
expect([Link]("All fields are required.")).[Link]();
});
});
MEDIUM
M4. Patient Medical Records
Create a React application called 'Patient Medical Records' with two components: Search (dropdown +
Show button) and Records (table + Next button). Load 3 patients from [Link]. Cycle
through records with the Next button, looping back to the first patient after the last.
Requirements
- Dropdown defaults to 'Select Patient' (selected + disabled).
- If Show is clicked without selecting, alert: 'Please select a patient name'.
- Show button displays the selected patient's records in a table with a Next button.
- Next cycles through patients by ID in ascending order, looping from last to first.
Sample Test Cases
Test Cases at a Glance
1. shows 'Select Patient' as default option
2. alerts when Show clicked without selection
3. no records table visible on initial load
[Link]
import { describe, expect, it, vi } from "vitest";
import { render, screen, fireEvent } from "@testing-library/react";
import App from "./App";
describe("M4 - Patient Medical Records (sample)", () => {
it("shows 'Select Patient' as default option", () => {
render(<App />);
expect([Link]("option", { name: /select patient/i })).toBeDisabled();
});
it("alerts when Show clicked without selection", () => {
render(<App />);
const alertMock = [Link](window, "alert").mockImplementation(() => {});
[Link]([Link]("button", { name: /show/i }));
expect(alertMock).toHaveBeenCalledWith("Please select a patient name");
[Link]();
});
it("no records table visible on initial load", () => {
render(<App />);
expect([Link]("table")).[Link]();
});
});
Hidden Test Cases
Test Cases at a Glance
1. shows records table after valid patient selection and Show click
2. Next button shows next patient's records
3. Next loops back to first patient after last
4. dropdown contains exactly 3 patient options plus placeholder
[Link]
import { describe, expect, it, vi } from "vitest";
import { render, screen, fireEvent } from "@testing-library/react";
import App from "./App";
describe("M4 - Patient Medical Records (hidden)", () => {
it("shows records table after valid patient selection and Show click", () => {
render(<App />);
[Link]([Link]("combobox"), {
target: { value: "1" } // first patient ID
});
[Link]([Link]("button", { name: /show/i }));
expect([Link]("table")).toBeInTheDocument();
expect([Link]("button", { name: /next/i })).toBeInTheDocument();
});
it("Next button shows next patient's records", () => {
render(<App />);
[Link]([Link]("combobox"), { target: { value: "1" } });
[Link]([Link]("button", { name: /show/i }));
const firstName = [Link]("table").textContent;
[Link]([Link]("button", { name: /next/i }));
expect([Link]("table").textContent).[Link](firstName);
});
it("Next loops back to first patient after last", () => {
render(<App />);
[Link]([Link]("combobox"), { target: { value: "1" } });
[Link]([Link]("button", { name: /show/i }));
const firstContent = [Link]("table").textContent;
// Click Next until looped back (assuming 3 patients)
[Link]([Link]("button", { name: /next/i }));
[Link]([Link]("button", { name: /next/i }));
[Link]([Link]("button", { name: /next/i }));
expect([Link]("table").textContent).toBe(firstContent);
});
it("dropdown contains exactly 3 patient options plus placeholder", () => {
render(<App />);
const options = [Link]("option");
expect(options).toHaveLength(4); // 1 placeholder + 3 patients
});
});
MEDIUM
M5. Blog Post
Create a React application called 'Blog Post' allowing users to create, display, and delete blog posts.
Posts appear in a grid layout, each inside a box with a title, description, and Delete button.
Requirements
- Two inputs: title (text) and description (textarea).
- 'Create' button adds a post only if both fields have values.
- After creation, both fields are cleared.
- Each post card shows title, description, and a Delete button to remove it.
Sample Test Cases
Test Cases at a Glance
1. renders title input, description textarea, and Create button
2. creates a post and displays it
3. does not create post if title is empty
4. Delete button removes a post
[Link]
import { describe, expect, it } from "vitest";
import { render, screen, fireEvent } from "@testing-library/react";
import BlogPost from "./BlogPost";
describe("M5 - Blog Post (sample)", () => {
it("renders title input, description textarea, and Create button", () => {
render(<BlogPost />);
expect([Link](/title/i)).toBeInTheDocument();
expect([Link](/description/i)).toBeInTheDocument();
expect([Link]("button", { name: /create/i })).toBeInTheDocument();
});
it("creates a post and displays it", () => {
render(<BlogPost />);
[Link]([Link](/title/i), { target: { value: "My
Post" } });
[Link]([Link](/description/i), { target: {
value: "My Desc" } });
[Link]([Link]("button", { name: /create/i }));
expect([Link]("My Post")).toBeInTheDocument();
});
it("does not create post if title is empty", () => {
render(<BlogPost />);
[Link]([Link](/description/i), { target: {
value: "Desc" } });
[Link]([Link]("button", { name: /create/i }));
expect([Link]("Desc")).[Link]();
});
it("Delete button removes a post", () => {
render(<BlogPost />);
[Link]([Link](/title/i), { target: { value:
"ToDelete" } });
[Link]([Link](/description/i), { target: {
value: "Desc" } });
[Link]([Link]("button", { name: /create/i }));
[Link]([Link]("button", { name: /delete/i }));
expect([Link]("ToDelete")).[Link]();
});
});
Hidden Test Cases
Test Cases at a Glance
1. clears inputs after post creation
2. does not create post if description is empty
3. deleting one post does not affect others
[Link]
import { describe, expect, it } from "vitest";
import { render, screen, fireEvent } from "@testing-library/react";
import BlogPost from "./BlogPost";
describe("M5 - Blog Post (hidden)", () => {
it("clears inputs after post creation", () => {
render(<BlogPost />);
const titleInput = [Link](/title/i);
const descInput = [Link](/description/i);
[Link](titleInput, { target: { value: "T" } });
[Link](descInput, { target: { value: "D" } });
[Link]([Link]("button", { name: /create/i }));
expect(titleInput).toHaveValue("");
expect(descInput).toHaveValue("");
});
it("does not create post if description is empty", () => {
render(<BlogPost />);
[Link]([Link](/title/i), { target: { value:
"Title" } });
[Link]([Link]("button", { name: /create/i }));
expect([Link]("button", { name: /delete/i })).toHaveLength(0);
});
it("deleting one post does not affect others", () => {
render(<BlogPost />);
["Post A", "Post B"].forEach(title => {
[Link]([Link](/title/i), { target: { value:
title } });
[Link]([Link](/description/i), { target: {
value: "Desc" } });
[Link]([Link]("button", { name: /create/i }));
});
const deleteBtns = [Link]("button", { name: /delete/i });
[Link](deleteBtns[0]);
expect([Link]("Post A")).[Link]();
expect([Link]("Post B")).toBeInTheDocument();
});
});
MEDIUM
M6. Slideshow
Create a React 'Basic Slideshow' component that takes a slides prop (array of {title, text} objects) and
supports Next, Prev, and Restart navigation. Prev and Restart are disabled on the first slide; Next is
disabled on the last slide.
Sample Test Cases
Test Cases at a Glance
1. renders first slide on load
2. Prev and Restart are disabled on first slide
3. Next advances to second slide
4. Next is disabled on last slide
[Link]
import { describe, expect, it } from "vitest";
import { render, screen, fireEvent } from "@testing-library/react";
import Slides from "./Slides";
const slides = [
{ title: "Slide 1", text: "Content 1" },
{ title: "Slide 2", text: "Content 2" },
{ title: "Slide 3", text: "Content 3" },
];
describe("M6 - Slideshow (sample)", () => {
it("renders first slide on load", () => {
render(<Slides slides={slides} />);
expect([Link]("Slide 1")).toBeInTheDocument();
});
it("Prev and Restart are disabled on first slide", () => {
render(<Slides slides={slides} />);
expect([Link]("button", { name: /prev/i })).toBeDisabled();
expect([Link]("button", { name: /restart/i })).toBeDisabled();
});
it("Next advances to second slide", () => {
render(<Slides slides={slides} />);
[Link]([Link]("button", { name: /next/i }));
expect([Link]("Slide 2")).toBeInTheDocument();
});
it("Next is disabled on last slide", () => {
render(<Slides slides={slides} />);
[Link]([Link]("button", { name: /next/i }));
[Link]([Link]("button", { name: /next/i }));
expect([Link]("button", { name: /next/i })).toBeDisabled();
});
});
Hidden Test Cases
Test Cases at a Glance
1. Restart returns to first slide from any position
2. Prev goes back to previous slide
3. Prev and Restart enabled after advancing from first slide
4. single-slide deck disables Next, Prev, and Restart
[Link]
import { describe, expect, it } from "vitest";
import { render, screen, fireEvent } from "@testing-library/react";
import Slides from "./Slides";
const slides = [
{ title: "S1", text: "T1" },
{ title: "S2", text: "T2" },
{ title: "S3", text: "T3" },
];
describe("M6 - Slideshow (hidden)", () => {
it("Restart returns to first slide from any position", () => {
render(<Slides slides={slides} />);
[Link]([Link]("button", { name: /next/i }));
[Link]([Link]("button", { name: /next/i }));
[Link]([Link]("button", { name: /restart/i }));
expect([Link]("S1")).toBeInTheDocument();
});
it("Prev goes back to previous slide", () => {
render(<Slides slides={slides} />);
[Link]([Link]("button", { name: /next/i }));
[Link]([Link]("button", { name: /prev/i }));
expect([Link]("S1")).toBeInTheDocument();
});
it("Prev and Restart enabled after advancing from first slide", () => {
render(<Slides slides={slides} />);
[Link]([Link]("button", { name: /next/i }));
expect([Link]("button", { name: /prev/i })).[Link]();
expect([Link]("button", { name: /restart/i })).[Link]();
});
it("single-slide deck disables Next, Prev, and Restart", () => {
render(<Slides slides={[{ title: "Only", text: "One" }]} />);
expect([Link]("button", { name: /next/i })).toBeDisabled();
expect([Link]("button", { name: /prev/i })).toBeDisabled();
expect([Link]("button", { name: /restart/i })).toBeDisabled();
});
});
MEDIUM
M7. Employee Validation Form
Create a React app with an EmployeeValidationForm component collecting Name, Email, Employee ID,
and Joining Date — each with inline validation. Submit is disabled until all fields are valid.
Validation Rules
- Name: At least 4 characters, only letters and spaces.
- Email: Valid email format (user@[Link]).
- Employee ID: Exactly 6 numeric digits.
- Joining Date: Must not be a future date.
Sample Test Cases
Test Cases at a Glance
1. submit button is disabled initially
2. shows name error for short name
3. shows email error for invalid format
4. shows employee ID error for non-6-digit input
[Link]
import { describe, expect, it } from "vitest";
import { render, screen, fireEvent } from "@testing-library/react";
import EmployeeValidationForm from "./EmployeeValidationForm";
describe("M7 - Employee Validation Form (sample)", () => {
it("submit button is disabled initially", () => {
render(<EmployeeValidationForm />);
expect([Link]("button", { name: /submit/i })).toBeDisabled();
});
it("shows name error for short name", () => {
render(<EmployeeValidationForm />);
[Link]([Link](/name/i), { target: { value: "AB" } });
expect([Link](/at least 4 characters/i)).toBeInTheDocument();
});
it("shows email error for invalid format", () => {
render(<EmployeeValidationForm />);
[Link]([Link](/email/i), { target: { value:
"notanemail" } });
expect([Link](/valid email/i)).toBeInTheDocument();
});
it("shows employee ID error for non-6-digit input", () => {
render(<EmployeeValidationForm />);
[Link]([Link](/employee id/i), { target: { value:
"123" } });
expect([Link](/exactly 6 digits/i)).toBeInTheDocument();
});
});
Hidden Test Cases
Test Cases at a Glance
1. submit enabled when all fields valid
2. resets form after submission
3. rejects name with numbers
4. rejects future joining date
[Link]
import { describe, expect, it } from "vitest";
import { render, screen, fireEvent } from "@testing-library/react";
import EmployeeValidationForm from "./EmployeeValidationForm";
describe("M7 - Employee Validation Form (hidden)", () => {
it("submit enabled when all fields valid", () => {
render(<EmployeeValidationForm />);
[Link]([Link](/name/i), { target: { value: "John Doe"
} });
[Link]([Link](/email/i), { target: { value: "j@[Link]"
} });
[Link]([Link](/employee id/i), { target: { value:
"123456" } });
[Link]([Link](/joining date/i), { target: { value:
"2020-01-01" } });
expect([Link]("button", { name: /submit/i })).[Link]();
});
it("resets form after submission", () => {
render(<EmployeeValidationForm />);
[Link]([Link](/name/i), { target: { value: "John Doe"
} });
[Link]([Link](/email/i), { target: { value: "j@[Link]"
} });
[Link]([Link](/employee id/i), { target: { value:
"123456" } });
[Link]([Link](/joining date/i), { target: { value:
"2020-01-01" } });
[Link]([Link]("button", { name: /submit/i }));
expect([Link](/name/i)).toHaveValue("");
});
it("rejects name with numbers", () => {
render(<EmployeeValidationForm />);
[Link]([Link](/name/i), { target: { value: "John1" }
});
expect([Link](/only contain letters and
spaces/i)).toBeInTheDocument();
});
it("rejects future joining date", () => {
render(<EmployeeValidationForm />);
[Link]([Link](/joining date/i), { target: { value:
"2099-01-01" } });
expect([Link](/cannot be in the future/i)).toBeInTheDocument();
});
});
MEDIUM
M8. CryptoRank Exchange
Build a 'CryptoRank Exchange' React app that estimates cryptocurrency coins received for a given fiat
amount. Show a table of Exchange Rate and Number of Coins. Validate amount against available
balance.
Conversion: Number of Coins = Amount * Exchange Rate (8 decimal places)
Error Messages
- Empty input: 'Amount cannot be empty'
- Amount < 0.01: 'Amount cannot be less than 0.01'
- Amount > balance: 'Amount cannot exceed the available balance'
Sample Test Cases
Test Cases at a Glance
1. shows 0.00000000 by default for all coins
2. shows error for empty input
3. shows error when amount below 0.01
[Link]
import { describe, expect, it } from "vitest";
import { render, screen, fireEvent } from "@testing-library/react";
import CryptoRankExchange from "./CryptoRankExchange";
describe("M8 - CryptoRank Exchange (sample)", () => {
it("shows 0.00000000 by default for all coins", () => {
render(<CryptoRankExchange />);
const zeros = [Link]("0.00000000");
expect([Link]).toBeGreaterThanOrEqual(1);
});
it("shows error for empty input", () => {
render(<CryptoRankExchange />);
const input = [Link]("spinbutton");
[Link](input, { target: { value: "" } });
expect([Link]("Amount cannot be empty")).toBeInTheDocument();
});
it("shows error when amount below 0.01", () => {
render(<CryptoRankExchange />);
[Link]([Link]("spinbutton"), { target: { value: "0.001" }
});
expect([Link]("Amount cannot be less than
0.01")).toBeInTheDocument();
});
});
Hidden Test Cases
Test Cases at a Glance
1. shows n/a for all coins on invalid amount
2. calculates coins to 8 decimal places for valid amount
3. table updates dynamically as amount changes
4. error message for exceeding balance
[Link]
import { describe, expect, it } from "vitest";
import { render, screen, fireEvent } from "@testing-library/react";
import CryptoRankExchange from "./CryptoRankExchange";
describe("M8 - CryptoRank Exchange (hidden)", () => {
it("shows n/a for all coins on invalid amount", () => {
render(<CryptoRankExchange />);
[Link]([Link]("spinbutton"), { target: { value: "999999" }
});
const nas = [Link]("n/a");
expect([Link]).toBeGreaterThanOrEqual(1);
});
it("calculates coins to 8 decimal places for valid amount", () => {
render(<CryptoRankExchange />);
[Link]([Link]("spinbutton"), { target: { value: "100" } });
const coinValues = [Link](/^\d+\.\d{8}$/);
expect([Link]).toBeGreaterThanOrEqual(1);
});
it("table updates dynamically as amount changes", () => {
render(<CryptoRankExchange />);
const input = [Link]("spinbutton");
[Link](input, { target: { value: "50" } });
const val50 = [Link](/^\d+\.\d{8}$/);
[Link](input, { target: { value: "100" } });
const val100 = [Link](/^\d+\.\d{8}$/);
expect(val50[0]?.textContent).[Link](val100[0]?.textContent);
});
it("error message for exceeding balance", () => {
render(<CryptoRankExchange balance={500} />);
[Link]([Link]("spinbutton"), { target: { value: "501" } });
expect([Link]("Amount cannot exceed the available
balance")).toBeInTheDocument();
});
});
MEDIUM
M9. useQuery Custom Hook
Implement a useQuery hook that manages a promise resolution for data fetching.
Signature
[Link]
function useQuery<T>(
fn: () => Promise<T>,
deps: DependencyList = []
): QueryResult<T>
// Returns one of:
// { status: "loading" }
// { status: "error"; error: Error }
// { status: "success"; data: T }
Sample Test Cases
Test Cases at a Glance
1. returns loading status initially
2. returns success status with data on resolution
3. returns error status on rejection
[Link]
import { describe, expect, it, vi } from "vitest";
import { renderHook, waitFor } from "@testing-library/react";
import { useQuery } from "./useQuery";
describe("M9 - useQuery (sample)", () => {
it("returns loading status initially", () => {
const { result } = renderHook(() =>
useQuery(() => new Promise(() => {}))
);
expect([Link]).toBe("loading");
});
it("returns success status with data on resolution", async () => {
const { result } = renderHook(() =>
useQuery(() => [Link](42))
);
await waitFor(() => expect([Link]).toBe("success"));
if ([Link] === "success") {
expect([Link]).toBe(42);
}
});
it("returns error status on rejection", async () => {
const { result } = renderHook(() =>
useQuery(() => [Link](new Error("Oops")))
);
await waitFor(() => expect([Link]).toBe("error"));
if ([Link] === "error") {
expect([Link]).toBe("Oops");
}
});
});
Hidden Test Cases
Test Cases at a Glance
1. re-runs fn when deps change
2. does not update state for stale async responses
3. defaults deps to [] and runs only once without deps
[Link]
import { describe, expect, it, vi } from "vitest";
import { renderHook, waitFor, act } from "@testing-library/react";
import { useQuery } from "./useQuery";
describe("M9 - useQuery (hidden)", () => {
it("re-runs fn when deps change", async () => {
let count = 0;
const fn = [Link](() => [Link](++count));
const { result, rerender } = renderHook(
({ id }) => useQuery(() => fn(), [id]),
{ initialProps: { id: 1 } }
);
await waitFor(() => expect([Link]).toBe("success"));
rerender({ id: 2 });
await waitFor(() => {
if ([Link] === "success") expect([Link]).toBe(2);
});
expect(fn).toHaveBeenCalledTimes(2);
});
it("does not update state for stale async responses", async () => {
let resolve1!: (v: number) => void;
let resolve2!: (v: number) => void;
const { result, rerender } = renderHook(
({ id }) => useQuery(
() => id === 1 ? new Promise(r => { resolve1 = r; }) : new Promise(r => {
resolve2 = r; }),
[id]
),
{ initialProps: { id: 1 } }
);
rerender({ id: 2 });
act(() => { resolve2(200); });
await waitFor(() => expect([Link]).toBe("success"));
act(() => { resolve1(100); }); // stale
await new Promise(r => setTimeout(r, 50));
if ([Link] === "success") expect([Link]).toBe(200);
});
it("defaults deps to [] and runs only once without deps", async () => {
const fn = [Link](() => [Link]("ok"));
const { rerender } = renderHook(() => useQuery(fn));
rerender();
await new Promise(r => setTimeout(r, 50));
expect(fn).toHaveBeenCalledTimes(1);
});
});
MEDIUM
M10. useArray Custom Hook
Implement a useArray hook that manages an array of items with utility methods: push, update, remove,
filter, set, and clear.
Sample Test Cases
Test Cases at a Glance
1. initializes with default value
2. push adds item to end
3. remove removes item at index
4. clear empties the array
[Link]
import { describe, expect, it } from "vitest";
import { renderHook, act } from "@testing-library/react";
import { useArray } from "./useArray";
describe("M10 - useArray (sample)", () => {
it("initializes with default value", () => {
const { result } = renderHook(() => useArray(["apple", "banana"]));
expect([Link]).toEqual(["apple", "banana"]);
});
it("push adds item to end", () => {
const { result } = renderHook(() => useArray<string>([]));
act(() => [Link]("orange"));
expect([Link]).toEqual(["orange"]);
});
it("remove removes item at index", () => {
const { result } = renderHook(() => useArray(["a","b","c"]));
act(() => [Link](1));
expect([Link]).toEqual(["a","c"]);
});
it("clear empties the array", () => {
const { result } = renderHook(() => useArray([1,2,3]));
act(() => [Link]());
expect([Link]).toEqual([]);
});
});
Hidden Test Cases
Test Cases at a Glance
1. update replaces element at index
2. filter retains only matching elements
3. set replaces entire array
4. push to empty array creates single-element array
5. remove at out-of-bounds index does not throw
[Link]
import { describe, expect, it } from "vitest";
import { renderHook, act } from "@testing-library/react";
import { useArray } from "./useArray";
describe("M10 - useArray (hidden)", () => {
it("update replaces element at index", () => {
const { result } = renderHook(() => useArray(["apple","banana"]));
act(() => [Link](1, "grape"));
expect([Link]).toEqual(["apple","grape"]);
});
it("filter retains only matching elements", () => {
const { result } = renderHook(() => useArray(["apple","banana","avocado"]));
act(() => [Link](f => [Link]("a")));
expect([Link]).toEqual(["apple","banana","avocado"]);
});
it("set replaces entire array", () => {
const { result } = renderHook(() => useArray([1,2,3]));
act(() => [Link]([10,20]));
expect([Link]).toEqual([10,20]);
});
it("push to empty array creates single-element array", () => {
const { result } = renderHook(() => useArray<number>([]));
act(() => [Link](99));
expect([Link]).toHaveLength(1);
expect([Link][0]).toBe(99);
});
it("remove at out-of-bounds index does not throw", () => {
const { result } = renderHook(() => useArray([1,2]));
expect(() => act(() => [Link](10))).[Link]();
});
});
HARD QUESTIONS
HARD
H1. Article Sorting
Create a React 'Article Sorting' component that receives an articles prop (array of {title, upvotes, date}).
By default sort by upvotes descending. 'Most Upvoted' sorts by upvotes desc; 'Most Recent' sorts by
date desc.
Sample Test Cases
Test Cases at a Glance
1. renders articles sorted by upvotes by default
2. Most Recent button sorts by date descending
3. Most Upvoted button returns to upvote sort
[Link]
import { describe, expect, it } from "vitest";
import { render, screen, fireEvent, within } from "@testing-library/react";
import Articles from "./Articles";
const articles = [
{ title: "Low Upvotes", upvotes: 10, date: "2024-01-01" },
{ title: "High Upvotes", upvotes: 100, date: "2023-06-15" },
{ title: "Most Recent", upvotes: 50, date: "2024-12-01" },
];
describe("H1 - Article Sorting (sample)", () => {
it("renders articles sorted by upvotes by default", () => {
render(<Articles articles={articles} />);
const rows = [Link]("row").slice(1); // skip header
expect(rows[0]).toHaveTextContent("High Upvotes");
});
it("Most Recent button sorts by date descending", () => {
render(<Articles articles={articles} />);
[Link]([Link]("button", { name: /most recent/i }));
const rows = [Link]("row").slice(1);
expect(rows[0]).toHaveTextContent("Most Recent");
});
it("Most Upvoted button returns to upvote sort", () => {
render(<Articles articles={articles} />);
[Link]([Link]("button", { name: /most recent/i }));
[Link]([Link]("button", { name: /most upvoted/i }));
const rows = [Link]("row").slice(1);
expect(rows[0]).toHaveTextContent("High Upvotes");
});
});
Hidden Test Cases
Test Cases at a Glance
1. all articles are rendered
2. upvote sort: last row is lowest upvotes
3. date sort: last row is oldest date
4. renders as a table with headers
[Link]
import { describe, expect, it } from "vitest";
import { render, screen, fireEvent } from "@testing-library/react";
import Articles from "./Articles";
const articles = [
{ title: "A", upvotes: 5, date: "2022-01-01" },
{ title: "B", upvotes: 20, date: "2024-06-01" },
{ title: "C", upvotes: 1, date: "2023-12-01" },
];
describe("H1 - Article Sorting (hidden)", () => {
it("all articles are rendered", () => {
render(<Articles articles={articles} />);
expect([Link]("row").length - 1).toBe([Link]);
});
it("upvote sort: last row is lowest upvotes", () => {
render(<Articles articles={articles} />);
const rows = [Link]("row").slice(1);
expect(rows[[Link]-1]).toHaveTextContent("C");
});
it("date sort: last row is oldest date", () => {
render(<Articles articles={articles} />);
[Link]([Link]("button", { name: /most recent/i }));
const rows = [Link]("row").slice(1);
expect(rows[[Link]-1]).toHaveTextContent("A");
});
it("renders as a table with headers", () => {
render(<Articles articles={articles} />);
expect([Link]("table")).toBeInTheDocument();
expect([Link]("columnheader", { name: /title/i
})).toBeInTheDocument();
});
});
HARD
H2. Word Omitter
Create a React 'Word Omitter' app with a WordOmitter component taking an omitWords prop. As the
user types, the output omits specified words in real-time. A toggle switches between omit mode and
show-all mode. A clear button resets both input and output.
Sample Test Cases
Test Cases at a Glance
1. initial output is empty
2. omits specified words from output
3. toggle shows all words
4. clear button clears input and output
[Link]
import { describe, expect, it } from "vitest";
import { render, screen, fireEvent } from "@testing-library/react";
import WordOmitter from "./WordOmitter";
describe("H2 - Word Omitter (sample)", () => {
it("initial output is empty", () => {
render(<WordOmitter omitWords={["a"]} />);
expect([Link]("output").textContent).toBe("");
});
it("omits specified words from output", () => {
render(<WordOmitter omitWords={["a"]} />);
[Link]([Link]("textbox"), { target: { value: "This is a
test" } });
expect([Link]("output").textContent).[Link](" a ");
});
it("toggle shows all words", () => {
render(<WordOmitter omitWords={["a"]} />);
[Link]([Link]("textbox"), { target: { value: "This is a
test" } });
[Link]([Link]("button", { name: /show all/i }));
expect([Link]("output").textContent).toContain("a");
});
it("clear button clears input and output", () => {
render(<WordOmitter omitWords={["a"]} />);
[Link]([Link]("textbox"), { target: { value: "Hello" } });
[Link]([Link]("button", { name: /clear/i }));
expect([Link]("textbox")).toHaveValue("");
expect([Link]("output").textContent).toBe("");
});
});
Hidden Test Cases
Test Cases at a Glance
1. omits multiple words simultaneously
2. toggle button label changes between modes
3. output is empty when input is empty
4. does not omit partial word matches
[Link]
import { describe, expect, it } from "vitest";
import { render, screen, fireEvent } from "@testing-library/react";
import WordOmitter from "./WordOmitter";
describe("H2 - Word Omitter (hidden)", () => {
it("omits multiple words simultaneously", () => {
render(<WordOmitter omitWords={["the","is"]} />);
[Link]([Link]("textbox"), {
target: { value: "the sky is blue" }
});
const output = [Link]("output").textContent;
expect(output).[Link]("the");
expect(output).[Link]("is");
expect(output).toContain("sky");
expect(output).toContain("blue");
});
it("toggle button label changes between modes", () => {
render(<WordOmitter omitWords={["x"]} />);
const btn = [Link]("button", { name: /show all/i });
[Link](btn);
expect([Link]("button", { name: /omit/i })).toBeInTheDocument();
});
it("output is empty when input is empty", () => {
render(<WordOmitter omitWords={["word"]} />);
const input = [Link]("textbox");
[Link](input, { target: { value: "word" } });
[Link](input, { target: { value: "" } });
expect([Link]("output").textContent).toBe("");
});
it("does not omit partial word matches", () => {
render(<WordOmitter omitWords={["is"]} />);
[Link]([Link]("textbox"), {
target: { value: "This island is nice" }
});
const output = [Link]("output").textContent;
expect(output).toContain("This");
expect(output).toContain("island");
expect(output).[Link](/\bis\b/);
});
});
HARD
H3. Nested Checkboxes
Build a component displaying a hierarchical checkbox structure. Parent state is derived from children:
all checked → parent checked, some checked → parent indeterminate (shows dash), none checked →
parent unchecked. Toggling a parent checks/unchecks all descendants.
Sample Test Cases
Test Cases at a Glance
1. renders parent and children checkboxes
2. all unchecked initially
3. checking parent checks all children
[Link]
import { describe, expect, it } from "vitest";
import { render, screen, fireEvent } from "@testing-library/react";
import NestedCheckboxes from "./NestedCheckboxes";
const tree = {
label: "Parent",
children: [
{ label: "Child A", children: [] },
{ label: "Child B", children: [] },
]
};
describe("H3 - Nested Checkboxes (sample)", () => {
it("renders parent and children checkboxes", () => {
render(<NestedCheckboxes node={tree} />);
expect([Link]("Parent")).toBeInTheDocument();
expect([Link]("Child A")).toBeInTheDocument();
expect([Link]("Child B")).toBeInTheDocument();
});
it("all unchecked initially", () => {
render(<NestedCheckboxes node={tree} />);
[Link]("checkbox").forEach(cb => {
expect(cb).[Link]();
});
});
it("checking parent checks all children", () => {
render(<NestedCheckboxes node={tree} />);
[Link]([Link]("Parent"));
expect([Link]("Child A")).toBeChecked();
expect([Link]("Child B")).toBeChecked();
});
});
Hidden Test Cases
Test Cases at a Glance
1. parent is indeterminate when only some children are checked
2. parent becomes checked when all children checked
3. unchecking parent unchecks all descendants
4. root becomes indeterminate when only one branch checked
[Link]
import { describe, expect, it } from "vitest";
import { render, screen, fireEvent } from "@testing-library/react";
import NestedCheckboxes from "./NestedCheckboxes";
const tree = {
label: "Root",
children: [
{ label: "A", children: [{ label: "A1", children: [] }, { label: "A2",
children: [] }] },
{ label: "B", children: [] },
]
};
describe("H3 - Nested Checkboxes (hidden)", () => {
it("parent is indeterminate when only some children are checked", () => {
render(<NestedCheckboxes node={tree} />);
[Link]([Link]("A1"));
const aCheckbox = [Link]("A") as HTMLInputElement;
expect([Link]).toBe(true);
});
it("parent becomes checked when all children checked", () => {
render(<NestedCheckboxes node={tree} />);
[Link]([Link]("A1"));
[Link]([Link]("A2"));
expect([Link]("A")).toBeChecked();
});
it("unchecking parent unchecks all descendants", () => {
render(<NestedCheckboxes node={tree} />);
[Link]([Link]("Root"));
[Link]([Link]("Root"));
expect([Link]("A1")).[Link]();
expect([Link]("A2")).[Link]();
});
it("root becomes indeterminate when only one branch checked", () => {
render(<NestedCheckboxes node={tree} />);
[Link]([Link]("B"));
const root = [Link]("Root") as HTMLInputElement;
expect([Link]).toBe(true);
});
});
HARD
H4. Design Cancellable Function
Write a function cancellable(generator) that accepts a generator object and returns [cancelFn, promise].
The promise resolves with the generator's return value. If cancel() is called before completion, throw
'Cancelled' into the generator. Handle caught/uncaught cancellations appropriately.
Sample Test Cases
Test Cases at a Glance
1. resolves when not cancelled
2. rejects with 'Cancelled' when cancel is called
3. passes resolved promise values back to generator
[Link]
import { describe, expect, it, vi } from "vitest";
import { cancellable } from "./cancellable";
describe("H4 - Cancellable Function (sample)", () => {
it("resolves when not cancelled", async () => {
function* gen() { return 42; }
const [, promise] = cancellable(gen());
await expect(promise).[Link](42);
});
it("rejects with 'Cancelled' when cancel is called", async () => {
function* gen() { yield new Promise(r => setTimeout(r, 200)); return "done"; }
const [cancel, promise] = cancellable(gen());
setTimeout(cancel, 50);
await expect(promise).[Link]("Cancelled");
});
it("passes resolved promise values back to generator", async () => {
function* gen() {
const val = yield [Link](10);
return (val as number) + 5;
}
const [, promise] = cancellable(gen());
await expect(promise).[Link](15);
});
});
Hidden Test Cases
Test Cases at a Glance
1. cancel after completion has no effect
2. caught cancellation allows generator to return value
3. rejected promise throws error back to generator
4. accumulates values across multiple yields
[Link]
import { describe, expect, it } from "vitest";
import { cancellable } from "./cancellable";
describe("H4 - Cancellable Function (hidden)", () => {
it("cancel after completion has no effect", async () => {
function* gen() { return 99; }
const [cancel, promise] = cancellable(gen());
const result = await promise;
expect(() => cancel()).[Link]();
expect(result).toBe(99);
});
it("caught cancellation allows generator to return value", async () => {
function* gen() {
try { yield new Promise(r => setTimeout(r, 200)); }
catch (e) { return "caught"; }
}
const [cancel, promise] = cancellable(gen());
setTimeout(cancel, 50);
await expect(promise).[Link]("caught");
});
it("rejected promise throws error back to generator", async () => {
function* gen() {
try { yield [Link](new Error("Boom")); }
catch (e: any) { return [Link]; }
}
const [, promise] = cancellable(gen());
await expect(promise).[Link]("Boom");
});
it("accumulates values across multiple yields", async () => {
function* gen() {
const a = yield [Link](3) as unknown as number;
const b = yield [Link](7) as unknown as number;
return a + b;
}
const [, promise] = cancellable(gen());
await expect(promise).[Link](10);
});
});
HARD
H5. Memoize II
Given a function fn, return a memoized version that uses === to compare inputs. Use a tree of Maps
keyed by arguments (not [Link]) to achieve O(1) per-call lookup. Arguments are compared by
reference identity.
Constraints
- 1 ≤ [Link] ≤ 10^5
- inputs[i][j] != NaN
Sample Test Cases
Test Cases at a Glance
1. caches result for identical primitive args
2. calls fn again for different args
3. different object references are not cache hits
[Link]
import { describe, expect, it } from "vitest";
import { memoize } from "./memoize";
describe("H5 - Memoize II (sample)", () => {
it("caches result for identical primitive args", () => {
let calls = 0;
const fn = (a: number, b: number) => { calls++; return a + b; };
const memo = memoize(fn);
expect(memo(2, 2)).toBe(4);
expect(memo(2, 2)).toBe(4);
expect(calls).toBe(1);
});
it("calls fn again for different args", () => {
let calls = 0;
const fn = (a: number, b: number) => { calls++; return a + b; };
const memo = memoize(fn);
memo(1, 2);
memo(2, 1);
expect(calls).toBe(2);
});
it("different object references are not cache hits", () => {
let calls = 0;
const fn = (a: object, b: object) => { calls++; return { ...a, ...b }; };
const memo = memoize(fn);
memo({}, {});
memo({}, {});
expect(calls).toBe(2);
});
});
Hidden Test Cases
Test Cases at a Glance
1. same object reference is a cache hit
2. handles zero-argument function
3. handles 100k calls efficiently (no O(N) per call)
4. memoizes functions with 3+ args
[Link]
import { describe, expect, it } from "vitest";
import { memoize } from "./memoize";
describe("H5 - Memoize II (hidden)", () => {
it("same object reference is a cache hit", () => {
let calls = 0;
const fn = (a: object) => { calls++; return a; };
const memo = memoize(fn);
const o = {};
memo(o); memo(o); memo(o);
expect(calls).toBe(1);
});
it("handles zero-argument function", () => {
let calls = 0;
const fn = () => { calls++; return 42; };
const memo = memoize(fn);
memo(); memo(); memo();
expect(calls).toBe(1);
});
it("handles 100k calls efficiently (no O(N) per call)", () => {
const fn = (a: number) => a * 2;
const memo = memoize(fn);
const start = [Link]();
for (let i = 0; i < 100000; i++) memo(i % 100);
expect([Link]() - start).toBeLessThan(500);
});
it("memoizes functions with 3+ args", () => {
let calls = 0;
const fn = (a: number, b: number, c: number) => { calls++; return a+b+c; };
const memo = memoize(fn);
memo(1,2,3); memo(1,2,3);
expect(calls).toBe(1);
memo(1,2,4);
expect(calls).toBe(2);
});
});
HARD
H6. Join Two Arrays by ID
Given two arrays arr1 and arr2 of objects with unique integer id fields, return a merged array sorted by
id ascending. If IDs overlap, arr2 values override arr1 for conflicting keys. Non-overlapping IDs appear
unmodified.
Sample Test Cases
Test Cases at a Glance
1. concatenates non-overlapping IDs sorted by id
2. arr2 overrides arr1 on key conflict
3. preserves unique keys from arr1
[Link]
import { describe, expect, it } from "vitest";
import { join } from "./join";
describe("H6 - Join Two Arrays by ID (sample)", () => {
it("concatenates non-overlapping IDs sorted by id", () => {
const arr1 = [{ id: 1, x: 1 }, { id: 2, x: 9 }];
const arr2 = [{ id: 3, x: 5 }];
expect(join(arr1, arr2)).toEqual([{ id:1, x:1 }, { id:2, x:9 }, { id:3, x:5
}]);
});
it("arr2 overrides arr1 on key conflict", () => {
const arr1 = [{ id: 2, x: 3, y: 6 }];
const arr2 = [{ id: 2, x: 10, y: 20 }];
const result = join(arr1, arr2);
expect(result[0]).toMatchObject({ id: 2, x: 10, y: 20 });
});
it("preserves unique keys from arr1", () => {
const arr1 = [{ id: 1, b: { b: 94 }, y: 48 }];
const arr2 = [{ id: 1, b: { c: 84 }, v: [1, 3] }];
const result = join(arr1, arr2);
expect(result[0].y).toBe(48);
});
});
Hidden Test Cases
Test Cases at a Glance
1. result is sorted by id ascending
2. handles empty arr1
3. handles empty arr2
4. deep object value from arr2 replaces arr1 value entirely
5. result length equals unique IDs count
[Link]
import { describe, expect, it } from "vitest";
import { join } from "./join";
describe("H6 - Join Two Arrays by ID (hidden)", () => {
it("result is sorted by id ascending", () => {
const arr1 = [{ id: 3, x: 3 }, { id: 1, x: 1 }];
const arr2 = [{ id: 2, x: 2 }];
const result = join(arr1, arr2);
expect([Link](r => [Link])).toEqual([1, 2, 3]);
});
it("handles empty arr1", () => {
const arr2 = [{ id: 5, x: 5 }];
expect(join([], arr2)).toEqual([{ id: 5, x: 5 }]);
});
it("handles empty arr2", () => {
const arr1 = [{ id: 1, x: 1 }];
expect(join(arr1, [])).toEqual([{ id: 1, x: 1 }]);
});
it("deep object value from arr2 replaces arr1 value entirely", () => {
const arr1 = [{ id: 1, b: { b: 94 } }];
const arr2 = [{ id: 1, b: { c: 84 } }];
const result = join(arr1, arr2);
expect((result[0].b as any).b).toBeUndefined();
expect((result[0].b as any).c).toBe(84);
});
it("result length equals unique IDs count", () => {
const arr1 = [{ id: 1 }, { id: 2 }];
const arr2 = [{ id: 2 }, { id: 3 }];
expect(join(arr1, arr2)).toHaveLength(3);
});
});
HARD
H7. Retry an Asynchronous Function N Times
Given an async function fn (no arguments, returns a promise) and retries count, return a new promise
that retries fn on rejection up to retries additional times. Resolves with the first success value; rejects
with the final error after all attempts fail.
Sample Test Cases
Test Cases at a Glance
1. resolves on first attempt
2. retries and eventually resolves
3. rejects after exhausting retries
[Link]
import { describe, expect, it } from "vitest";
import { retryFn } from "./retryFn";
describe("H7 - Retry Async N Times (sample)", () => {
it("resolves on first attempt", async () => {
const fn = () => [Link]("OK");
await expect(retryFn(fn, 3)).[Link]("OK");
});
it("retries and eventually resolves", async () => {
let count = 0;
const fn = () => new Promise<string>((res, rej) => {
count++;
count < 3 ? rej("fail") : res("done");
});
await expect(retryFn(fn, 5)).[Link]("done");
});
it("rejects after exhausting retries", async () => {
const fn = () => [Link]("Error");
await expect(retryFn(fn, 2)).[Link]("Error");
});
});
Hidden Test Cases
Test Cases at a Glance
1. retries exactly N times before rejecting
2. retries=0 means no retries, rejects immediately on failure
3. resolves with correct value after retries
4. rejects with the last error, not the first
[Link]
import { describe, expect, it } from "vitest";
import { retryFn } from "./retryFn";
describe("H7 - Retry Async N Times (hidden)", () => {
it("retries exactly N times before rejecting", async () => {
let calls = 0;
const fn = () => { calls++; return [Link]("fail"); };
await retryFn(fn, 3).catch(() => {});
expect(calls).toBe(4); // 1 initial + 3 retries
});
it("retries=0 means no retries, rejects immediately on failure", async () => {
let calls = 0;
const fn = () => { calls++; return [Link]("fail"); };
await retryFn(fn, 0).catch(() => {});
expect(calls).toBe(1);
});
it("resolves with correct value after retries", async () => {
let n = 0;
const fn = () => new Promise<number>((res, rej) => ++n < 3 ? rej(n) : res(n));
await expect(retryFn(fn, 5)).[Link](3);
});
it("rejects with the last error, not the first", async () => {
let n = 0;
const fn = () => [Link](`err${++n}`);
await expect(retryFn(fn, 2)).[Link]("err3");
});
});
HARD
H8. Execute Async Functions Sequentially
Given an array of async functions (each returning a promise), execute them one after another in
sequence. Return a promise that resolves with an array of all resolved values in order, or rejects
immediately if any promise rejects.
Sample Test Cases
Test Cases at a Glance
1. resolves with all values in order
2. rejects immediately when one function rejects
3. executes sequentially, not in parallel
[Link]
import { describe, expect, it } from "vitest";
import { promiseAll } from "./promiseAll";
describe("H8 - Execute Async Functions Sequentially (sample)", () => {
it("resolves with all values in order", async () => {
const fns = [
() => [Link](1),
() => [Link](2),
() => [Link](3),
];
await expect(promiseAll(fns)).[Link]([1, 2, 3]);
});
it("rejects immediately when one function rejects", async () => {
const fns = [
() => [Link](1),
() => [Link]("Error"),
() => [Link](3),
];
await expect(promiseAll(fns)).[Link]("Error");
});
it("executes sequentially, not in parallel", async () => {
const log: number[] = [];
const fns = [
() => new Promise<number>(r => setTimeout(() => { [Link](1); r(1); }, 50)),
() => new Promise<number>(r => { [Link](2); r(2); }),
];
await promiseAll(fns);
expect(log).toEqual([1, 2]);
});
});
Hidden Test Cases
Test Cases at a Glance
1. empty array resolves with empty array
2. single function resolves with single-element array
3. stops executing after first rejection
4. resolves in correct order even with different delays
[Link]
import { describe, expect, it } from "vitest";
import { promiseAll } from "./promiseAll";
describe("H8 - Execute Async Functions Sequentially (hidden)", () => {
it("empty array resolves with empty array", async () => {
await expect(promiseAll([])).[Link]([]);
});
it("single function resolves with single-element array", async () => {
await expect(promiseAll([() => [Link](42)])).[Link]([42]);
});
it("stops executing after first rejection", async () => {
let executedThird = false;
const fns = [
() => [Link](1),
() => [Link]("stop"),
() => { executedThird = true; return [Link](3); },
];
await promiseAll(fns).catch(() => {});
expect(executedThird).toBe(false);
});
it("resolves in correct order even with different delays", async () => {
const fns = [
() => new Promise<number>(r => setTimeout(() => r(1), 30)),
() => new Promise<number>(r => setTimeout(() => r(2), 10)),
() => new Promise<number>(r => setTimeout(() => r(3), 20)),
];
await expect(promiseAll(fns)).[Link]([1, 2, 3]);
});
});
HARD
H9. Add Timeout to an Asynchronous Function
Given an async function fn and a time limit t (ms), return a new promise. Resolve with fn's result if it
completes within t ms. Otherwise reject with 'Time Limit Exceeded'. If fn itself rejects, propagate that
rejection.
Sample Test Cases
Test Cases at a Glance
1. resolves when fn completes within time limit
2. rejects with 'Time Limit Exceeded' when fn is too slow
3. propagates fn's own rejection
[Link]
import { describe, expect, it } from "vitest";
import { timeLimit } from "./timeLimit";
describe("H9 - Add Timeout to Async Function (sample)", () => {
it("resolves when fn completes within time limit", async () => {
const fn = () => new Promise<number>(r => setTimeout(() => r(42), 50));
await expect(timeLimit(fn, 200)).[Link](42);
});
it("rejects with 'Time Limit Exceeded' when fn is too slow", async () => {
const fn = () => new Promise<number>(r => setTimeout(() => r(42), 300));
await expect(timeLimit(fn, 100)).[Link]("Time Limit Exceeded");
});
it("propagates fn's own rejection", async () => {
const fn = () => [Link]("Server Error");
await expect(timeLimit(fn, 200)).[Link]("Server Error");
});
});
Hidden Test Cases
Test Cases at a Glance
1. resolves exactly at the boundary is still a success
2. does not resolve after timeout fires
3. t=0 rejects immediately
4. works with multiple concurrent calls independently
[Link]
import { describe, expect, it, vi } from "vitest";
import { timeLimit } from "./timeLimit";
describe("H9 - Add Timeout to Async Function (hidden)", () => {
it("resolves exactly at the boundary is still a success", async () => {
const fn = () => new Promise<number>(r => setTimeout(() => r(1), 100));
// Slightly over should still time out
await expect(timeLimit(fn, 50)).[Link]("Time Limit Exceeded");
});
it("does not resolve after timeout fires", async () => {
let resolved = false;
const fn = () => new Promise<void>(r => setTimeout(() => { resolved = true;
r(); }, 300));
await timeLimit(fn, 100).catch(() => {});
await new Promise(r => setTimeout(r, 400));
// Even if fn resolved later, the outer promise already rejected
expect(resolved).toBe(true); // fn ran, but the returned promise rejected
});
it("t=0 rejects immediately", async () => {
const fn = () => new Promise<number>(r => setTimeout(() => r(1), 100));
await expect(timeLimit(fn, 0)).[Link]("Time Limit Exceeded");
});
it("works with multiple concurrent calls independently", async () => {
const fast = () => new Promise<string>(r => setTimeout(() => r("fast"), 30));
const slow = () => new Promise<string>(r => setTimeout(() => r("slow"), 300));
const [r1, r2] = await [Link]([timeLimit(fast, 100),
timeLimit(slow, 100)]);
expect([Link]).toBe("fulfilled");
expect([Link]).toBe("rejected");
});
});
HARD
H10. Return the First Resolved Promise
Given an array of async functions (each returning a promise), return a new promise that resolves with
the first successfully resolved value. Reject only if all promises reject — with the array of all rejection
reasons. Do not use [Link].
Sample Test Cases
Test Cases at a Glance
1. resolves with the first to resolve
2. rejects with all reasons when all reject
3. resolves even if some reject before others resolve
[Link]
import { describe, expect, it } from "vitest";
import { promiseAny } from "./promiseAny";
describe("H10 - Return First Resolved Promise (sample)", () => {
it("resolves with the first to resolve", async () => {
const fns = [
() => new Promise<number>(r => setTimeout(() => r(5), 200)),
() => new Promise<number>(r => setTimeout(() => r(10), 100)),
];
await expect(promiseAny(fns)).[Link](10);
});
it("rejects with all reasons when all reject", async () => {
const fns = [
() => [Link]("A"),
() => [Link]("B"),
];
await expect(promiseAny(fns)).[Link](["A", "B"]);
});
it("resolves even if some reject before others resolve", async () => {
const fns = [
() => new Promise((_, rej) => setTimeout(() => rej("fail"), 50)),
() => new Promise<number>(r => setTimeout(() => r(99), 150)),
];
await expect(promiseAny(fns)).[Link](99);
});
});
Hidden Test Cases
Test Cases at a Glance
1. empty array rejects with empty array
2. single resolving function resolves
3. single rejecting function rejects with array of one reason
4. does not use [Link] (structural check)
5. rejection reasons array preserves original order
[Link]
import { describe, expect, it } from "vitest";
import { promiseAny } from "./promiseAny";
describe("H10 - Return First Resolved Promise (hidden)", () => {
it("empty array rejects with empty array", async () => {
await expect(promiseAny([])).[Link]([]);
});
it("single resolving function resolves", async () => {
await expect(promiseAny([() => [Link](7)])).[Link](7);
});
it("single rejecting function rejects with array of one reason", async () => {
await expect(promiseAny([() => [Link]("X")])).[Link](["X"]);
});
it("does not use [Link] (structural check)", async () => {
const original = [Link];
let used = false;
(Promise as any).any = (...args: any[]) => { used = true; return
original(...args); };
await promiseAny([() => [Link](1)]);
(Promise as any).any = original;
expect(used).toBe(false);
});
it("rejection reasons array preserves original order", async () => {
const fns = [
() => new Promise((_, r) => setTimeout(() => r("first"), 100)),
() => new Promise((_, r) => setTimeout(() => r("second"), 50)),
];
// Even though "second" rejects first in time, order should match input order
await expect(promiseAny(fns)).[Link](["first", "second"]);
});
});