DAY 07 · PRACTICE

Python · Java · JavaScript · SQL

📦 Topics: Logic · OOP · Arrays · DOM · SQL Joins ⏱ Est: 3–4 hrs total 🎯 Difficulty: 2/5 · Same as Day 6
01
Number Classifier — Palindrome, Armstrong & Prime 🔢
LOGICNUMBER THEORYEASY~30 min
📖 Scenario
These three checks appear in competitive programming, interviews, and school exams constantly. A Palindrome reads the same forwards and backwards (121, 1331). An Armstrong number equals the sum of its digits each raised to the power of the number of digits (153 = 1³+5³+3³). A Prime has exactly two divisors: 1 and itself.
🧠 Key Facts
Palindrome: Convert to string, compare with its reverse.
Armstrong (3-digit): Each digit cubed and summed equals the original number.
Prime: No number from 2 up to √n divides it evenly. 1 is NOT prime.
Armstrong examples: 1, 153, 370, 371, 407
Palindrome examples: 121, 131, 1221, 12321
Starter Code
def is_palindrome(n): pass # convert to string, check if it equals its reverse def is_armstrong(n): pass # sum of each digit ^ number_of_digits == n def is_prime(n): pass # check divisibility from 2 to sqrt(n) def classify(n): labels = [] if is_palindrome(n): labels.append("Palindrome") if is_armstrong(n): labels.append("Armstrong") if is_prime(n): labels.append("Prime") return labels if labels else ["None"] for n in [2, 11, 121, 153, 370, 1634]: print(f"{n}: {classify(n)}")
Your Tasks
  • Fill is_palindrome(n) — a number is a palindrome if it reads the same forwards and backwards Think about strings — Python has a clean one-liner for this
  • Fill is_armstrong(n) — for each digit, raise it to the power of total digit count, then sum all You'll need: how many digits does n have? How do you extract each digit?
  • Fill is_prime(n) — handle edge cases (n < 2), then check divisors You only need to check up to √n — think about why that is
  • Run classify() on the test numbers and verify the output matches
Expected Output
2: ['Prime'] 11: ['Palindrome', 'Prime'] 121: ['Palindrome'] 153: ['Armstrong'] 370: ['Armstrong'] 1634: ['Armstrong']
💡 One Hint
For Armstrong, you need two things: the count of digits in n, and each digit individually. Think about what happens when you convert n to a string — you can loop over characters, convert each back to int, and the length gives you the power. For prime, once you've checked up to √n, you know the rest — ask yourself: if n had a factor bigger than √n, what would its paired factor have to be?
02
Shopping Cart Analyser 🛒
LISTDICTEASY~25 min
📖 Scenario
Every e-commerce site has a cart — a list of items with names, prices, and quantities. You'll build functions to calculate totals, apply discounts, and find the most expensive item. This is the same logic behind Amazon, Flipkart, and every online store.
Starter Code
cart = [ {"item": "Laptop", "price": 45000, "qty": 1}, {"item": "Mouse", "price": 800, "qty": 2}, {"item": "Keyboard", "price": 1500, "qty": 1}, {"item": "Pen Drive","price": 600, "qty": 3}, {"item": "Charger", "price": 1200, "qty": 1}, ] # Write your code below
Your Tasks
  • Calculate the total bill — price × qty for each item, then sum all Each item contributes price * qty to the total
  • Apply a 10% discount on items where total item cost (price × qty) exceeds ₹1000 — return updated cart Create a new list; don't modify the original cart in place
  • Find the most expensive item by unit price
  • List item names where qty > 1 — "multi-quantity" items
Expected Output
Total bill: ₹51700 After discount: Laptop→₹40500, Mouse→₹720, Keyboard→₹1500, PenDrive→₹540, Charger→₹1200 Most expensive: Laptop (₹45000) Multi-qty items: ['Mouse', 'Pen Drive']
💡 One Hint
For the discount, the condition checks price × qty > 1000 for each item. When that's true, only the price changes — reduce it by 10%. Think: what is 90% of the original price? You'll need to decide whether to change the original dict or build a new one.
03
Employee Payroll System 👔
OOPINHERITANCEEASY~30 min
📖 Scenario
A company has two types of employees — full-time (fixed monthly salary) and part-time (paid per hour worked). Both share a base Employee class but calculate pay differently. This is the classic use case for method overriding — same method name, different logic per subclass.
Starter Code
class Employee: def __init__(self, name, emp_id): self.name = name self.emp_id = emp_id def get_pay(self): pass # override in subclasses def display(self): print(f"[{self.emp_id}] {self.name} — Pay: ₹{self.get_pay()}") class FullTime(Employee): def __init__(self, name, emp_id, monthly_salary): pass # call super, store monthly_salary def get_pay(self): pass # return monthly_salary class PartTime(Employee): def __init__(self, name, emp_id, hourly_rate, hours_worked): pass # call super, store rate and hours def get_pay(self): pass # return hourly_rate * hours_worked employees = [ FullTime("Rahul", "E01", 45000), PartTime("Priya", "E02", 500, 80), FullTime("Arjun", "E03", 52000), PartTime("Sneha", "E04", 600, 65), ] for e in employees: e.display()
Your Tasks
  • Complete both subclass __init__ methods — call super() then store the extra attributes
  • Override get_pay() in each subclass with the correct calculation
  • The display() method in the base class calls self.get_pay() — it will automatically use the right version. Run and verify output.
  • Add a function total_payroll(employees) that returns the sum of all pay
Expected Output
[E01] Rahul — Pay: ₹45000 [E02] Priya — Pay: ₹40000 [E03] Arjun — Pay: ₹52000 [E04] Sneha — Pay: ₹39000 Total payroll: ₹176000
💡 One Hint
The key concept here is polymorphism — display() calls self.get_pay(), and Python automatically picks the right version based on what type the object actually is. You don't need any if/else in display(). Focus on making sure each subclass stores what it needs and returns the right thing from get_pay().
04
Log File Analyser 📋
FILESTRINGEASY~25 min
📖 Scenario
Every server writes a log file — lines like [ERROR] Database timeout or [INFO] User logged in. You'll build a parser that counts log levels, finds all error lines, and writes a cleaned summary. This is exactly what tools like Splunk and Datadog do at massive scale.
Starter Code
# First create the log file log_data = """[INFO] Server started [INFO] User Rahul logged in [ERROR] Database connection failed [WARNING] High memory usage: 87% [ERROR] File not found: config.json [INFO] User Priya logged in [ERROR] Timeout on request /api/data [INFO] Backup completed""" with open("server.log", "w") as f: f.write(log_data) # Now write these functions: def parse_log(filename): pass def save_errors(errors, filename): pass
Your Tasks
  • parse_log() should read the file and return a dict: {"INFO": 4, "ERROR": 3, "WARNING": 1}
  • Also return a list of just the ERROR lines (full line text)
  • save_errors() should write each error line to errors.log, one per line
  • Print a formatted summary: total lines, count per level, and all error messages
Expected Output
Total lines: 8 INFO: 4 | ERROR: 3 | WARNING: 1 --- Errors --- [ERROR] Database connection failed [ERROR] File not found: config.json [ERROR] Timeout on request /api/data
💡 One Hint
Each log line starts with a level tag in brackets. Think about how to extract what's inside the brackets — there's a string method that finds text between two characters. Once you have the level string, building the count dict is just like the word frequency problem.
05
Sieve of Eratosthenes — Find All Primes 🔬
LOGICPRIMELISTMEDIUM~30 min
📖 Scenario
The Sieve of Eratosthenes is a 2000-year-old algorithm still used in cryptography today. Instead of checking each number individually, you mark off multiples — start with 2, cross out 4, 6, 8... then move to 3, cross out 6, 9, 12... whatever isn't crossed off is prime. It's dramatically faster than checking each number one by one.
🧠 How the Sieve Works
Create a list of True for indices 0 to N.
Mark index 0 and 1 as False (not prime).
For every number p starting at 2: if it's still True, mark all multiples (p×p, p×p+p, ...) as False.
At the end, indices that are still True are prime numbers.
Why start marking from p×p? Because smaller multiples were already marked by earlier primes.
Starter Code
def sieve(n): # Create a boolean list, all True initially is_prime = [True] * (n + 1) is_prime[0] = is_prime[1] = False p = 2 while p * p <= n: if is_prime[p]: pass # mark all multiples of p starting from p*p p += 1 return [i for i, v in enumerate(is_prime) if v] primes = sieve(50) print(primes) print(f"Count: {len(primes)}")
Your Tasks
  • Fill the inner logic — for each prime p, mark is_prime[p*p], is_prime[p*p+p], ... as False You need to step through multiples of p, starting at p*p, up to n
  • Run sieve(50) — verify you get 15 primes
  • Find all twin primes up to 50 — pairs like (3,5), (5,7), (11,13) where difference is exactly 2
  • Find the sum of all primes up to 100
Expected Output
Primes up to 50: [2,3,5,7,11,13,17,19,23,29,31,37,41,43,47] Count: 15 Twin primes: [(3,5),(5,7),(11,13),(17,19),(29,31),(41,43)] Sum of primes up to 100: 1060
💡 One Hint
For marking multiples, you want a loop that starts at p*p and jumps by p each time until it exceeds n. Python's range() can take a step argument. For twin primes, once you have the primes list, look at consecutive elements and check if the gap between them is 2.
01
Number Classifier — Palindrome, Armstrong & Prime ☕
LOGICNUMBER THEORYEASY~30 min
📖 Scenario
Same three logic checks as Python Q1 — but in Java. The algorithms are identical; only the syntax changes. This reinforces that logic is language-agnostic. Once you understand the approach, translating between languages is straightforward.
🧠 Java Syntax Reminders
String reverse: new StringBuilder(s).reverse().toString()
Number of digits: String.valueOf(n).length()
Power: Math.pow(base, exp) returns double — cast to int if needed
Square root: Math.sqrt(n)
Starter Code
public class NumberClassifier { static boolean isPalindrome(int n) { return false; // compare string form with its reverse } static boolean isArmstrong(int n) { return false; // digit ^ numDigits, sum == n } static boolean isPrime(int n) { return false; // check divisors 2..sqrt(n) } public static void main(String[] args) { int[] nums = {2, 11, 121, 153, 370, 1634}; for (int n : nums) { System.out.print(n + ": "); if (isPalindrome(n)) System.out.print("Palindrome "); if (isArmstrong(n)) System.out.print("Armstrong "); if (isPrime(n)) System.out.print("Prime"); System.out.println(); } } }
Your Tasks
  • Fill isPalindrome — convert n to String, compare with its StringBuilder reverse
  • Fill isArmstrong — extract digits via String.valueOf, raise each to power of digit count using Math.pow
  • Fill isPrime — loop from 2 to (int)Math.sqrt(n), return false if any divide evenly
  • Run and verify all 6 numbers match the expected output
Expected Output
2: Prime 11: Palindrome Prime 121: Palindrome 153: Armstrong 370: Armstrong 1634: Armstrong
💡 One Hint
For Armstrong in Java, looping over digits is easiest by converting to a String and iterating over characters with charAt(). Remember that charAt() gives you a char, not an int — to get the numeric value, subtract '0' from it: s.charAt(i) - '0'. For the power, Math.pow returns a double so you need to cast: (int) Math.pow(digit, numDigits).
02
Shopping Cart Analyser 🛒
ARRAYLISTOOPEASY~25 min
📖 Scenario
Same shopping cart problem as Python Q2 — but using a CartItem class and ArrayList. In Java, you use a class to bundle related data instead of a dict. The logic is identical — only the way you access data changes.
Starter Code
import java.util.*; class CartItem { String name; double price; int qty; CartItem(String n, double p, int q) { name=n; price=p; qty=q; } } public class ShoppingCart { public static void main(String[] args) { ArrayList<CartItem> cart = new ArrayList<>(); cart.add(new CartItem("Laptop", 45000, 1)); cart.add(new CartItem("Mouse", 800, 2)); cart.add(new CartItem("Keyboard", 1500, 1)); cart.add(new CartItem("Pen Drive",600, 3)); cart.add(new CartItem("Charger", 1200, 1)); // Task 1: total bill // Task 2: apply discount // Task 3: most expensive // Task 4: multi-qty items } }
Your Tasks
  • Calculate total bill — loop through cart, add item.price * item.qty
  • Apply 10% discount where price * qty > 1000 — update price directly on CartItem
  • Find the CartItem with the highest unit price
  • Collect names of items where qty > 1 into an ArrayList<String>
Expected Output
Total: ₹51700.0 Most expensive: Laptop (₹45000.0) Multi-qty: [Mouse, Pen Drive]
💡 One Hint
For the most expensive item, start by assuming the first item is the winner, then loop through the rest comparing prices. For the discount, you're modifying item.price directly inside the loop — Java passes object references, so changes to item fields inside a for-each loop are permanent.
03
Stream Pipeline Practice 🌊
STREAMSLAMBDAEASY~20 min
📖 Scenario
Java Streams let you chain operations on collections — filter, transform, summarise — without writing explicit loops. They're the Java equivalent of Python list comprehensions. Once you can read and write a stream pipeline, reading modern Java code becomes much easier.
Starter Code
import java.util.*; import java.util.stream.*; public class StreamPractice { public static void main(String[] args) { List<Integer> nums = Arrays.asList(1,2,3,4,5,6,7,8,9,10); List<String> names = Arrays.asList("rahul","PRIYA","arjun","SNEHA"); // Write your stream pipelines below } }
Your Tasks
  • Filter only even numbers from nums — collect to list
  • Map each number to its square — collect to list
  • Use .mapToInt().sum() to find the sum of all numbers
  • Normalize names — all title case (first letter uppercase, rest lowercase) s.substring(0,1).toUpperCase() + s.substring(1).toLowerCase()
  • Filter numbers greater than 5 and odd — collect to list
Expected Output
Even: [2, 4, 6, 8, 10] Squares: [1, 4, 9, 16, 25, 36, 49, 64, 81, 100] Sum: 55 Names: [Rahul, Priya, Arjun, Sneha] Above5 and odd: [7, 9]
💡 One Hint
For sum with streams, .mapToInt(x -> x).sum() converts to an IntStream that has a built-in sum() method — this is different from using .reduce(). For chaining two filters, you can either write two separate .filter() calls or combine them into one with &&.
04
Employee Payroll System 👔
OOPINHERITANCEEASY~30 min
📖 Scenario
Same payroll system as Python Q3. Full-time employees have a fixed salary; part-time are paid hourly. Both extend a base Employee class and override getPay(). The base class display() calls getPay() — Java picks the right version automatically at runtime.
Starter Code
abstract class Employee { String name; String empId; Employee(String name, String empId) { this.name=name; this.empId=empId; } abstract double getPay(); void display() { System.out.printf("[%s] %s — Pay: ₹%.0f%n", empId, name, getPay()); } } class FullTime extends Employee { double monthlySalary; FullTime(String n, String id, double salary) { super(n, id); /* store salary */ } double getPay() { return 0; /* return salary */ } } class PartTime extends Employee { double hourlyRate; int hoursWorked; PartTime(String n, String id, double rate, int hours) { super(n, id); /* store rate and hours */ } double getPay() { return 0; /* rate * hours */ } } public class Payroll { public static void main(String[] args) { Employee[] team = { new FullTime("Rahul","E01",45000), new PartTime("Priya","E02",500,80), new FullTime("Arjun","E03",52000), new PartTime("Sneha","E04",600,65), }; for (Employee e : team) e.display(); // also print total payroll } }
Your Tasks
  • Store the salary in FullTime constructor and return it from getPay()
  • Store rate and hours in PartTime constructor and return rate * hours from getPay()
  • Run the loop — verify display() picks the right getPay() for each type automatically
  • After the loop, calculate and print total payroll by summing e.getPay() for all employees
Expected Output
[E01] Rahul — Pay: ₹45000 [E02] Priya — Pay: ₹40000 [E03] Arjun — Pay: ₹52000 [E04] Sneha — Pay: ₹39000 Total payroll: ₹176000
💡 One Hint
Notice the base class uses abstract double getPay() — this forces every subclass to implement it, otherwise the code won't compile. The display() method can safely call getPay() without knowing which subclass it's dealing with. This is Java's polymorphism — the right version is selected at runtime, not compile time.
05
Custom Exception + File Logger 📂
EXCEPTIONFILE I/OMEDIUM~30 min
📖 Scenario
Build a student score validator that throws a custom exception when a score is out of range (below 0 or above 100), and logs every valid score to a file. This combines custom exceptions, file writing, and basic input validation — all common patterns in real Java applications.
Starter Code
import java.io.*; class InvalidScoreException extends Exception { InvalidScoreException(String msg) { super(msg); } } class ScoreLogger { String filename; ScoreLogger(String filename) { this.filename = filename; } void logScore(String name, int score) throws InvalidScoreException, IOException { // validate: score must be 0–100, else throw InvalidScoreException // if valid, write "name: score" to file using FileWriter (append mode) } } public class Main { public static void main(String[] args) { ScoreLogger logger = new ScoreLogger("scores.txt"); String[][] data = { {"Rahul","85"}, {"Priya","110"}, {"Arjun","72"}, {"Sneha","-5"}, {"Vikram","91"} }; for (String[] d : data) { try { logger.logScore(d[0], Integer.parseInt(d[1])); System.out.println("Logged: " + d[0]); } catch (InvalidScoreException e) { System.out.println("Invalid: " + d[0] + " — " + e.getMessage()); } catch (IOException e) { System.out.println("File error: " + e.getMessage()); } } } }
Your Tasks
  • In logScore() — throw InvalidScoreException("Score X is out of range 0-100") if score < 0 or score > 100
  • If valid, append name + ": " + score + "\n" to the file using FileWriter in append mode
  • Run the test data — Priya (110) and Sneha (-5) should throw, the other three should log
  • Read scores.txt back and print its contents to verify only valid entries were saved
Expected Output
Logged: Rahul Invalid: Priya — Score 110 is out of range 0-100 Logged: Arjun Invalid: Sneha — Score -5 is out of range 0-100 Logged: Vikram --- scores.txt --- Rahul: 85 Arjun: 72 Vikram: 91
💡 One Hint
FileWriter's second argument controls append mode — new FileWriter(filename, true) adds to the file without overwriting; false or no second argument would erase it each time. The custom exception just needs to pass the message to super() — the try/catch in main handles the rest.
01
Palindrome & Armstrong Checker — Live UI 🔢
DOMNUMBER THEORYEASY~25 min
📖 Scenario
Build the same Palindrome + Armstrong checker — but as a live webpage. The user types a number in an input box, clicks a button, and the result appears instantly without a page reload. This is exactly how form validators work on every website.
Starter Code (HTML file)
<input id="numInput" type="number" placeholder="Enter a number"> <button onclick="check()">Check</button> <div id="result"></div> <script> function isPalindrome(n) { const s = String(n); return /* s equals its reverse */; } function isArmstrong(n) { const digits = String(n).split(""); const power = digits.length; return /* sum of digit^power equals n */; } function check() { const n = parseInt(document.getElementById("numInput").value); let labels = []; if (isPalindrome(n)) labels.push("Palindrome"); if (isArmstrong(n)) labels.push("Armstrong"); if (labels.length === 0) labels.push("None"); document.getElementById("result").textContent = n + ": " + labels.join(", "); } </script>
Your Tasks
  • Fill isPalindrome — in JS, strings can be reversed with .split("").reverse().join("")
  • Fill isArmstrong — digits is already an array of character strings; use Number(d) to convert each to a number, and ** for exponentiation
  • Test with 121 (palindrome), 153 (armstrong), 11 (palindrome), 100 (none)
  • Add a color to the result: green for a match, red for "None"
Expected Output
Input 121 → "121: Palindrome" (green) Input 153 → "153: Armstrong" (green) Input 11 → "11: Palindrome" (green) Input 100 → "100: None" (red)
💡 One Hint
JS string reverse is a three-step chain — split into characters, reverse the array, join back into a string. For Armstrong, reduce() is a clean way to sum: digits.reduce((sum, d) => sum + Number(d) ** power, 0). For the color, set result.style.color after determining the label.
02
Cart Total Calculator 🛒
ARRAY METHODSDOMEASY~25 min
📖 Scenario
Build a cart summary page that reads an array of items and displays the total bill, the most expensive item, and applies a discount. This is JavaScript's home turf — combining array methods with DOM updates to show live data.
Starter Code
const cart = [ { name: "Laptop", price: 45000, qty: 1 }, { name: "Mouse", price: 800, qty: 2 }, { name: "Keyboard", price: 1500, qty: 1 }, { name: "Pen Drive",price: 600, qty: 3 }, ]; // Write your code below
Your Tasks
  • Use .reduce() to calculate the total bill (price × qty for each item)
  • Use .reduce() or .sort() to find the most expensive item by unit price
  • Use .map() to apply 10% discount on items where price × qty > 1000 — return new array
  • Use .filter() to get names of items where qty > 1, then .map() to extract just the names
Expected Output
Total: ₹51700 Most expensive: Laptop (₹45000) Multi-qty items: ['Mouse', 'Pen Drive'] After discount total: ₹46440
💡 One Hint
For the total with reduce, your accumulator needs to add item.price * item.qty each iteration. For the discounted cart, return a new object in .map() — spread the original first (...item) then override the price property. This avoids mutating the original cart.
03
Closure Counter & Timer ⏱️
CLOSURESSETTIMEOUTEASY~20 min
📖 Scenario
A closure is a function that remembers variables from its outer scope even after that outer function has finished. It's one of the most important JS concepts — it powers private variables, counters, event handlers, and most of React's hooks under the hood.
🧠 What is a Closure?
Normal function: variables are gone when function ends.
Closure: the inner function keeps a reference to the outer variable — it stays alive.
Real use: making a counter that can't be tampered with from outside.
Starter Code
// Task 1: makeCounter — returns an object with increment, decrement, reset, value function makeCounter(start = 0) { let count = start; return { increment: () => { /* count++ */ }, decrement: () => { /* count-- */ }, reset: () => { /* count = start */ }, value: () => count }; } const c = makeCounter(10); // Task 2: countdown using setTimeout function countdown(from) { // print from, from-1, ..., 0 — each 500ms apart using setTimeout }
Your Tasks
  • Fill all four methods in makeCounter — each method should read/write the closed-over count variable
  • Test: increment 3 times, decrement once, check value (should be 12), then reset (should be 10)
  • Fill countdown(from) — use a loop with setTimeout(fn, i * 500) to print each number 500ms apart Each iteration of the loop schedules a callback at a different delay
  • Call countdown(5) and observe the output spaced 500ms apart in the console
Expected Output
c.value() after setup → 12 c.value() after reset → 10 countdown(5) → prints 5, 4, 3, 2, 1, 0 with 500ms gaps
💡 One Hint
For the closure, each arrow function inside the returned object automatically captures the count variable from makeCounter's scope. For countdown, setTimeout(() => console.log(from - i), i * 500) inside a for loop schedules each print at a different time. But beware — with var, all iterations share the same variable. Use let in the loop.
04
Fetch & Display — Async/Await 🌐
ASYNCFETCHEASY~25 min
📖 Scenario
Almost every website fetches data from an API — weather, user profiles, products. JavaScript's async/await makes this readable. You'll fetch from a free public API, parse the JSON response, and display it in HTML. This is the foundation of every modern web app.
🧠 async / await in Plain English
Problem: fetching data takes time — JS can't just pause and wait.
Solution: mark the function as async, then use await before anything that takes time.
JS pauses only that function, not the whole page.
Always wrap await in try/catch — network requests can fail!
Starter Code
<div id="output">Loading...</div> <button onclick="loadUser()">Load Random User</button> <script> async function loadUser() { const out = document.getElementById("output"); out.textContent = "Loading..."; try { const res = await fetch("https://randomuser.me/api/"); const data = await res.json(); const user = data.results[0]; // TODO: display name, email, location city } catch (err) { out.textContent = "Error: " + err.message; } } </script>
Your Tasks
  • After fetching, display the user's full name — the API gives user.name.first and user.name.last
  • Also display their email (user.email) and city (user.location.city)
  • Format it as a nice card using innerHTML with basic HTML tags
  • Add a loading spinner — show "⏳ Loading..." when the button is clicked, replace with data when done
Expected Output (example — will vary)
Name: John Smith Email: john.smith@example.com City: London
💡 One Hint
The response from the API is nested — data.results[0] gives you the user object. From there, name, email, and location are all top-level keys. Use out.innerHTML = `<b>Name:</b> ${...}<br>...` to format it nicely. The loading state is just setting textContent before the await, then replacing it after.
05
Todo List — Full CRUD in Browser ✅
DOMEVENTSMEDIUM~35 min
📖 Scenario
A todo list is the "Hello World" of interactive web apps — add tasks, mark them done, delete them. It exercises DOM manipulation, event handling, and array management all together. Build it without any library — pure vanilla JS.
Starter Code
<input id="taskInput" placeholder="Add a task..."> <button onclick="addTask()">Add</button> <ul id="taskList"></ul> <p id="count"></p> <script> let tasks = []; // array of {text, done} objects function addTask() { // get input value, push {text, done:false} to tasks, re-render } function toggleDone(index) { // flip tasks[index].done, re-render } function deleteTask(index) { // remove tasks[index], re-render } function render() { // clear #taskList, rebuild from tasks array // each <li> has click-to-toggle and a delete button // update #count with remaining tasks } </script>
Your Tasks
  • Fill addTask — get the input value, push to array, clear input, call render()
  • Fill render — loop through tasks, create <li> for each. Done tasks should have a strikethrough style
  • Each <li> needs: clicking the text calls toggleDone(i), a "×" button calls deleteTask(i)
  • Update the count — show "X tasks remaining" (count done=false items)
Expected Behaviour
Add "Buy milk" → appears in list Add "Do laundry" → appears below Click "Buy milk" → strikethrough (done) Click × on "Do laundry" → removed from list Counter updates: "1 task remaining"
💡 One Hint
The key pattern here is "re-render from state" — every function ends by calling render(), which completely rebuilds the list from the tasks array. This avoids getting confused by stale DOM. In render(), set taskList.innerHTML = "" first, then loop and append. Use inline onclick attributes with the index: onclick="toggleDone(${i})".
01
SELECT with Conditions 🔍
SELECTWHEREORDER BYEASY~20 min
📖 Scenario
You are the new DBA at a school. The principal wants several reports — top students, students from a specific city, sorted lists. All of these come from one table using SELECT with WHERE and ORDER BY. Every real dashboard query starts here.
🗄️ Concept: Filtering and Sorting
WHERE narrows rows — it's the equivalent of Python's if inside a loop.
ORDER BY col ASC/DESC sorts the result. Default is ASC (smallest first).
LIMIT n returns only the first n rows after sorting.
You can combine multiple conditions with AND / OR.
String values need single quotes: WHERE city = 'Delhi'
TABLE: students
idnamecitymarksgrade
1'Rahul''Delhi'72'B'
2'Priya''Mumbai'91'A'
3'Arjun''Delhi'38'F'
4'Sneha''Pune'85'A'
5'Vikram''Mumbai'55'C'
6'Anita''Delhi'94'A'
7'Rohan''Pune'43'F'
Your Tasks
  • Get all students from Delhi, ordered by marks descending
  • Get name and marks of students who scored above 80
  • Get the top 3 students by marks
  • Get students from Mumbai OR Pune who passed (marks >= 50)
① Delhi students by marks desc
idnamecitymarksgrade
6AnitaDelhi94A
1RahulDelhi72B
3ArjunDelhi38F
② Above 80
namemarks
Priya91
Sneha85
Anita94
③ Top 3 students
namemarks
Anita94
Priya91
Sneha85
④ Mumbai/Pune and passed
namecitymarks
PriyaMumbai91
SnehaPune85
VikramMumbai55
💡 One Hint
For Task 4 with two cities, think about whether AND or OR should connect your city conditions. If you use AND, you'd need a student to be from both cities simultaneously — which is impossible. Use IN as a cleaner alternative to multiple OR conditions: WHERE city IN ('Mumbai', 'Pune').
02
Aggregate Functions & GROUP BY 📊
GROUP BYCOUNTAVGHAVINGEASY~25 min
📖 Scenario
The principal now wants summary statistics — averages, counts per city, highest scores. These require aggregate functions. GROUP BY lets you split rows into buckets and run a function on each bucket separately — like Python's word frequency counter, but in one SQL line.
🗄️ Concept: GROUP BY vs HAVING vs WHERE
WHERE filters rows before any grouping — it cannot reference COUNT or AVG.
GROUP BY divides the remaining rows into groups based on a column's value.
HAVING filters those finished groups — it CAN reference COUNT, AVG, etc.
Rule: if your condition mentions an aggregate function, it must go in HAVING, not WHERE.
TABLE: students (same table)
idnamecitymarksgrade
1'Rahul''Delhi'72'B'
2'Priya''Mumbai'91'A'
3'Arjun''Delhi'38'F'
4'Sneha''Pune'85'A'
5'Vikram''Mumbai'55'C'
6'Anita''Delhi'94'A'
7'Rohan''Pune'43'F'
Your Tasks
  • Count students per city
  • Average marks per grade
  • Cities with more than 2 students Filtering on a COUNT belongs in HAVING, not WHERE
  • Highest marks per city
① Students per city
cityCOUNT(*)
Delhi3
Mumbai2
Pune2
② Avg marks per grade
gradeAVG(marks)
A90.0
B72.0
C55.0
F40.5
③ Cities with more than 2 students
cityCOUNT(*)
Delhi3
④ Max marks per city
cityMAX(marks)
Delhi94
Mumbai91
Pune85
💡 One Hint
Think of GROUP BY + HAVING as a two-stage pipeline: first GROUP BY creates the buckets, then HAVING acts as a filter on those buckets. You cannot use COUNT(*) in a WHERE clause because WHERE runs before the grouping — the groups don't exist yet when WHERE is evaluated.
03
INNER JOIN — Two Tables Together 🔗
INNER JOINONEASY~25 min
📖 Scenario
Real databases split data across multiple tables to avoid repetition. Students are in one table; their enrolled courses are in another. A JOIN stitches them together using a shared ID — like Python's dictionary lookup but in SQL. This is the most important SQL concept for any developer.
🗄️ Concept: INNER JOIN
INNER JOIN returns rows where the condition matches in BOTH tables. If a student has no enrollment, they don't appear. If an enrollment has no matching student, it doesn't appear either — only matching pairs survive.

Syntax: FROM tableA INNER JOIN tableB ON tableA.id = tableB.student_id
When two tables have a column with the same name, prefix with table name: students.id
TABLE: students
idnamecity
1'Rahul''Delhi'
2'Priya''Mumbai'
3'Arjun''Delhi'
4'Sneha''Pune'
TABLE: enrollments
idstudent_idcoursescore
11'Python'88
21'SQL'76
32'Python'95
43'Java'62
54'SQL'81
62'Java'70
Your Tasks
  • Get student name and course for every enrollment
  • Get name, course, and score for students who scored above 80
  • Count how many courses each student is enrolled in — use JOIN + GROUP BY
  • Get all Python students with their names and scores, sorted by score descending
① All enrollments with names
namecourse
RahulPython
RahulSQL
PriyaPython
ArjunJava
SnehaSQL
PriyaJava
② Score above 80
namecoursescore
RahulPython88
PriyaPython95
SnehaSQL81
③ Courses per student
namecourse_count
Rahul2
Priya2
Arjun1
Sneha1
④ Python students by score
namescore
Priya95
Rahul88
💡 One Hint
The JOIN condition tells SQL which rows to pair up — match every student row with every enrollment row where the IDs agree. Once joined, you can treat the combined result like a single wider table with columns from both sides. For Task 3, you're counting rows per name after joining — which means GROUP BY comes after the JOIN, not before it.
04
LEFT JOIN — Find Students with No Courses 🕵️
LEFT JOINNULLMEDIUM~25 min
📖 Scenario
INNER JOIN only shows students who have enrollments. But what if you need to find students who haven't enrolled in anything — maybe to send them a reminder? LEFT JOIN keeps all rows from the left table, filling NULL for any columns from the right table where no match exists.
🗄️ Concept: LEFT JOIN vs INNER JOIN
INNER JOIN: Only matched rows appear — like Python set intersection.
LEFT JOIN: All left-table rows appear. Right-table columns are NULL if no match — like Python's dict.get() with a None default.

Classic pattern to find "missing" records: LEFT JOIN then WHERE right_table.id IS NULL. This finds rows in the left table with no matching row in the right.
students (expanded)
idname
1'Rahul'
2'Priya'
3'Arjun'
4'Sneha'
5'Vikram'
enrollments (same as Q3)
idstudent_idcourse
11'Python'
21'SQL'
32'Python'
43'Java'
54'SQL'
Your Tasks
  • LEFT JOIN students with enrollments — show all students, with NULL where no enrollment Vikram has no enrollment — his enrollment columns should show NULL
  • Find students who are NOT enrolled in any course After a LEFT JOIN, which column would be NULL for unenrolled students?
  • Count enrollments per student — include students with zero enrollments Use COUNT(enrollments.id) not COUNT(*) — COUNT(*) counts nulls too
  • Show each student's name and their number of courses, ordered by course count descending
① LEFT JOIN result
namecourse
RahulPython
RahulSQL
PriyaPython
ArjunJava
SnehaSQL
VikramNULL
② Not enrolled
name
Vikram
④ Courses per student (desc)
namecourse_count
Rahul2
Priya1
Arjun1
Sneha1
Vikram0
💡 One Hint
The "find missing records" pattern always follows the same shape: LEFT JOIN, then filter WHERE the right table's primary key IS NULL. For counting with zeros included, the difference between COUNT(*) and COUNT(column) matters — COUNT(*) counts the row even if the column is NULL, but COUNT(enrollments.id) skips NULL values, giving you 0 for Vikram correctly.
05
INSERT, UPDATE & DELETE — Write Operations ✏️
INSERTUPDATEDELETEMEDIUM~25 min
📖 Scenario
The school data needs updating — a new student joined, a retake exam happened, and some records need cleanup. You'll use INSERT, UPDATE, and DELETE in sequence and verify the final state. In production, these operations are wrapped in transactions — but for now, practice the syntax.
🗄️ Concept: CRUD Write Operations
INSERT INTO table (col1, col2) VALUES (v1, v2) — adds a new row.
UPDATE table SET col = val WHERE condition — changes existing rows. Without WHERE, every row changes!
DELETE FROM table WHERE condition — removes rows. Without WHERE, the entire table is deleted!
Rule: always write and double-check your WHERE before running UPDATE or DELETE.
TABLE: students (starting state)
idnamecitymarksgrade
1'Rahul''Delhi'72'B'
2'Priya''Mumbai'91'A'
3'Arjun''Delhi'38'F'
4'Sneha''Pune'85'A'
5'Vikram''Mumbai'55'C'
6'Anita''Delhi'94'A'
7'Rohan''Pune'43'F'
Your Tasks — Run in order
  • INSERT a new student: Karan, Bangalore, marks 78, grade B
  • UPDATE Arjun's marks to 65 and grade to 'C' — he retook the exam You can SET multiple columns in one UPDATE statement
  • DELETE all students whose grade is still 'F' after the update above Think carefully — after ②, is Arjun still grade F?
  • Write a SELECT * to verify the final table — should have exactly 7 rows
④ Final table state (7 rows)
idnamecitymarksgrade
1RahulDelhi72B
2PriyaMumbai91A
3ArjunDelhi65C
4SnehaPune85A
5VikramMumbai55C
6AnitaDelhi94A
8KaranBangalore78B
💡 One Hint
Notice id=7 (Rohan) is gone and the new Karan gets id=8 — databases never reuse deleted IDs, they keep incrementing. For ③, trace through your tasks in order: after you UPDATE Arjun's grade to 'C', he no longer qualifies for the DELETE. Work out on paper exactly who has grade 'F' right before you run the DELETE.