0–5 min
Real Hook
5–25 min
Time vs Space
25–45 min
Live Coding
45–55 min
You Code It
55–60 min
Cheat Sheet
🔥 Real-World Hook — Your Contacts App (0–5 min)

Your phone has 500 contacts. You type "P" and in 0.2 seconds you see Priya, Pooja, Praveen. How does it find them so fast? It doesn't read all 500 names one by one — that would take 500 steps.

Now imagine WhatsApp receives a call from +91 98765 43210. It needs to show you whose number this is before you pick up. To do that instantly, it keeps a second copy of your contacts mapped by phone number — same data stored twice. That's 2× the memory, but the answer is instant.

This is the core trade-off of every data structure: you pay with time or you pay with space. Rarely both for free.

⏲ The Two Costs Every Developer Must Know (5–25 min)

⚡ Time Complexity — How many steps?

How many operations does it take to find, add, or delete an item?

O(1) = 1 step always. Instant. (HashMap lookup)
O(log n) = 10 steps for 1000 items. Fast. (Binary search)
O(n) = 1000 steps for 1000 items. Slow. (Linear scan)

💾 Space Complexity — How much memory?

How much extra memory does your data structure need beyond the raw data?

O(1) = no extra memory. (ArrayList — just the array)
O(n) = extra memory proportional to items. (HashMap — needs hash table overhead)
O(2n) = double your data. (Two HashMaps for bidirectional lookup)

📞 The Contacts App Story — 4 Structures, 1 Problem

Same data: 500 contacts (name + phone). Four different structures. Wildly different results.

📆 Option 1 — ArrayList (the naive way)

Store contacts in a list. To find "Priya", start from index 0, check every name until you hit it.

Search: O(n) — 500 checks worst case Space: O(1) — no overhead, just the array

⚠ Works fine for 50 contacts. For 5,000,000 contacts (like WhatsApp), this is 5 million checks per search. The app freezes.

📚 Option 2 — Sorted ArrayList + Binary Search (smart)

Sort contacts A–Z. To find "Priya": open the middle (M), "P" is after M, open the middle of the right half (T), "P" is before T… found in 9 steps from 500 contacts.

Search: O(log n) — 9 checks for 500 contacts Space: O(1) — same array, just sorted

⚠ Adding a new contact? You must shift every element after it to the right. Insert is O(n). Great for read-heavy apps, bad if contacts change often.

⚡ Option 3 — HashMap (the right tool)

Map name → phone number. To find "Priya": the HashMap computes a hash of "Priya" and goes directly to that slot. No scanning.

Search: O(1) — always 1 step Space: O(n) — ~2x more than a plain array (hash table overhead)

✓ This is exactly what your phone's Contacts app uses internally. Fast to look up by name. You trade a bit of memory for instant speed.

Memory layout for 500 contacts in HashMap vs ArrayList
ArrayList (500 contacts)
500 contacts (raw data)
HashMap (500 contacts) — same data, more memory
500 contacts (raw data)
hash table overhead — extra memory for O(1) speed
⚠ This extra memory is the price you pay for O(1) lookup. Worth it for most apps.
⚠ Option 4 — Two HashMaps (bidirectional lookup)

WhatsApp needs both: look up number by name AND look up name by number (caller ID). Solution: store two HashMaps — one forward, one reverse.

Search by name: O(1) Search by number: O(1) Space: O(2n) — your data stored TWICE
Memory with TWO HashMaps
name→number HashMap (500 entries)
number→name HashMap (same 500, stored again)
2x hash overhead

This is a real trade-off: double the memory for instant two-way lookup. WhatsApp makes this trade because caller ID speed matters more than RAM cost on a phone.

💻 Live Coding — Build the Contact Book (25–45 min)

We'll build all four versions and measure the difference.

import java.util.*;

public class ContactBook {

    // ── Option 1: ArrayList ─────────────────────────────────────────────────
    private List<String[]> listContacts = new ArrayList<>();

    public void addToList(String name, String phone) {
        listContacts.add(new String[]{name, phone});
    }

    public String findByNameSlow(String name) {
        int steps = 0;
        for (String[] c : listContacts) {   // O(n) — checks every single contact
            steps++;
            if (c[0].equals(name)) {
                System.out.println("  ArrayList: found in " + steps + " steps");
                return c[1];
            }
        }
        return null;
    }

    // ── Option 3: HashMap (one-way) ─────────────────────────────────────────
    private Map<String, String> nameToPhone = new HashMap<>();

    public void addToMap(String name, String phone) {
        nameToPhone.put(name, phone);          // O(1) insert
    }

    public String findByNameFast(String name) {
        System.out.println("  HashMap: found in 1 step (O(1))");
        return nameToPhone.get(name);           // O(1) — always 1 step
    }

    // ── Option 4: Two HashMaps (bidirectional — WhatsApp caller ID) ─────────
    private Map<String, String> phoneToName = new HashMap<>(); // EXTRA memory!

    public void addBidirectional(String name, String phone) {
        nameToPhone.put(name, phone);   // store forward
        phoneToName.put(phone, name);   // store REVERSE — costs 2x space
    }

    public String whoseNumberIsThis(String phone) {
        return phoneToName.get(phone);      // O(1) — paid with space, not time
    }

    public static void main(String[] args) {
        ContactBook book = new ContactBook();

        // Add 500 contacts to both structures
        for (int i = 1; i <= 499; i++) {
            book.addToList("Contact" + i,  "9000000" + i);
            book.addToMap("Contact" + i,   "9000000" + i);
        }
        // Add "Priya" as the LAST contact — worst case for ArrayList
        book.addToList("Priya", "9876543210");
        book.addBidirectional("Priya", "9876543210");

        System.out.println("Searching for Priya...");
        book.findByNameSlow("Priya");     // 500 steps!
        book.findByNameFast("Priya");     // 1 step

        System.out.println("\nIncoming call: 9876543210");
        String caller = book.whoseNumberIsThis("9876543210");
        System.out.println("Caller: " + caller); // "Priya" — O(1), paid with 2x space
    }
}
💡 Expected Output — See the difference yourself
Searching for Priya...
  ArrayList: found in 500 steps
  HashMap:   found in 1 step (O(1))

Incoming call: 9876543210
Caller: Priya

📄 When to Use Each Structure

Every structure has one job it does best. Pick the one whose weakness you can afford to ignore.

Structure Search Insert Space Use it when… Real Example
ArrayList O(n) O(1) (end) O(n) You need ordered data, loop through it, rarely search by value Zomato order history — your past orders shown newest first. You scroll through them in order, never jump to "order #347".
HashMap O(1) O(1) O(n) +overhead You need instant lookup by a key (name, ID, username, token) Contacts app — type "Priya", instant result. Also: every login session (token → userId), every product page (productId → details).
Two HashMaps O(1) both ways O(1) O(2n) You need instant lookup in both directions (name↔phone, userId↔username) WhatsApp caller ID — name→number when you call, number→name when someone calls you. Same data stored twice, O(1) both ways.
Stack (Deque) O(n) O(1) (top) O(n) Last-in-first-out: undo, back navigation, call stack VS Code Ctrl+Z — every keystroke pushed onto a Stack. Ctrl+Z pops the last one. Browser Back button works the same way.
Queue O(n) O(1) (end) O(n) First-in-first-out: jobs processed in arrival order Swiggy restaurant queue — orders arrive and are cooked in sequence. First order placed = first order prepared. Also: OTP delivery, email sending jobs.

🌎 Where You See This in Real Apps

📱
Instagram — Follow / Following
Each user has two HashSets: following and followers. To check "Does Priya follow Ravi?" — O(1) lookup in Ravi's followers HashSet. They spend 2× the memory so the check is instant for 1 billion users.
▶
YouTube — Video Recommendations
Your watch history is an ArrayList (ordered, append-only). Your preferences are a HashMap (category → score). ArrayList for order, HashMap for speed — two structures working together.
🔒
Any Login System — Session Token
When you log in, the server creates a token like "abc123xyz" and stores it in a HashMap: token → userId. Every API request: look up the token in O(1). This is how every app knows who you are without re-logging you in every minute.
🕒
VS Code — Ctrl+Z (Undo)
Every edit you make is pushed onto a Stack. Ctrl+Z pops the last edit (LIFO). The stack grows as you type, shrinks as you undo. Simple structure, powerful behaviour.
🎯 You Code It (45–55 min)
  1. Copy the ContactBook class into IntelliJ and run it — see the 500-step vs 1-step output yourself
  2. Add a method deleteContact(String name) that removes from both HashMaps (think: if you remove forward, what happens to the reverse map?)
  3. Add a method renameContact(String oldName, String newName) — update both maps correctly
  4. Space challenge: Add a field private List<String> recentSearches = new ArrayList<>() and a method search(String name) that looks up the contact AND saves the search term to the list (max 10 recent searches — remove the oldest if over 10)
  5. Think out loud: If your app had 10 million contacts, which structure would you choose? What if RAM was expensive (embedded device)? What if speed was critical (server)? Tell the instructor your reasoning.

📄 Day 2 Cheat Sheet — Data Structures & Complexity

Big-O Quick Reference

O(1)Constant — always 1 step (HashMap.get)
O(log n)Logarithmic — halves each time (binary search)
O(n)Linear — scans all items (ArrayList search)
O(n log n)Sorting (Collections.sort)
O(n²)Nested loops — avoid on large data

ArrayList

new ArrayList<>()Create
list.add(x)Add to end — O(1)
list.get(i)By index — O(1)
list.contains(x)Search — O(n)
list.remove(i)Remove — O(n) shifts
Collections.sort(list)Sort — O(n log n)

HashMap

new HashMap<>()Create
map.put(key, val)Insert/update — O(1)
map.get(key)Lookup — O(1)
map.getOrDefault(k, def)Safe get
map.containsKey(k)Key check — O(1)
map.remove(key)Delete — O(1)

The Space–Time Trade-off Rule

Need fast search?Use HashMap — costs extra memory
Need both-way lookup?Two HashMaps — costs 2× memory
Memory is tight?ArrayList + binary search (sorted)
LIFO (undo/back)?Stack (ArrayDeque)
FIFO (queue/jobs)?Queue (LinkedList)
Unique items only?HashSet — O(1) contains