In class · frontal Part 1 of 2 · ~90 minutes

Vibe Coding a real assignment

We warm up, watch how to solve a Problem Set 1 question with the AI — ask, visualize, test, improve — and then you get 30 minutes to solve the next one yourselves.

🔥

1 · Warm-up

A tiny bit-trick to get Copilot Chat talking.

🎬

2 · Guided demo

longest_run — the full vibe-coding loop.

🏁

3 · Your challenge

reverse_bits in 30 minutes.

🔥 Warm-up · ispow2(n)

Open a new file in VS Code, open Copilot Chat, and get a first solution on the board together. Nothing fancy — we just want everyone's assistant awake.

The task

Write ispow2(n) that returns True if n is a power of two, else False. It must work for huge integers like 2**70, and 0 is not a power of two.

Assignment rule for Section 1: no bin, len, max, logarithms, strings, or imports — just integer bit operations.

You → Copilot

Write a Python function ispow2(n) that returns True iff n is a power of two. Constraints: no imports, no built-ins like bin/len, no logarithms or strings — only integer bit operations. 0 is not a power of two.

Copilot

Here's the classic constant-time bit trick:

sol1.py
def ispow2(n):
    # A power of two has exactly one set bit, so n & (n-1) clears it to 0.
    # Guard n > 0 because 0 & -1 == 0 would falsely pass.
    return n > 0 and (n & (n - 1)) == 0
💬 The one-liner to say out loud

“Give the AI the constraints, not just the goal.” Notice we told it about the forbidden built-ins up front — otherwise it happily reaches for bin().


🎬 Guided demo · longest_run(n)

Now the real thing. longest_run(n) returns the length of the longest run of consecutive 1 bits in the binary representation of n. It's a walkthrough of how to drive the AI through a full loop: ask → visualize → test → catch a bug → fix → iterate.

🎬 Live in class

Your instructor builds this live with Copilot, right here, in real time. The same steps are written out below so you can follow along or revisit them later.

Turn 1 — Ask for a first solution
You → Copilot

Write longest_run(n): length of the longest run of consecutive 1-bits in n's binary form. longest_run(0) is 0, longest_run(0b1110111) is 3. Add a docstring.

Copilot

Sure — the simplest approach walks the binary string:

sol1.py — first attempt
def longest_run(n):
    """Return the length of the longest run of consecutive 1 bits in n."""
    best = 0
    for group in bin(n)[2:].split("0"):   # ⚠️ uses bin() and strings
        if len(group) > best:
            best = len(group)
    return best
⚠️ It works… but it breaks the rules

This is correct, and if we wrote tests they'd pass. But the assignment forbids bin, len, and strings in Section 1. Passing tests ≠ acceptable solution. Hold that thought — we'll come back and fix it in Turn 4.

Turn 2 — Ask for a visualization

Before we trust anything, let's see it. Understanding beats guessing.

You → Copilot

I want to understand this visually. Lay out the bits of n as a grid and highlight the longest run of 1s for a few example inputs.

Copilot

Here's a small helper that prints each bit and marks the winning run — try it with 0b1110111, 0b101010, and (1<<5)-1:

🎛️ Live in the slides — play with it

Type a number and watch the bits. The red cells are the longest run of 1s — exactly what the function should return.

try 119 (0b1110111), 42 (0b101010), 31 (0b11111)
Turn 3 — Ask for tests
You → Copilot

Write pytest tests for longest_run. Cover the edge cases: 0, a single 1, all-ones like (1<<8)-1, alternating bits 0b101010, a run in the middle, and a large number.

Copilot

Here you go:

test_sol1.py
from sol1 import longest_run

def test_zero():
    assert longest_run(0) == 0

def test_single_bit():
    assert longest_run(0b1) == 1

def test_all_ones():
    assert longest_run((1 << 8) - 1) == 8      # 0b11111111

def test_alternating():
    assert longest_run(0b101010) == 1

def test_run_in_middle():
    assert longest_run(0b1110111) == 3

def test_large():
    assert longest_run((1 << 70) - 1) == 70    # arbitrary-size int
✅ Run them: python -m pytest -q test_sol1.py

All green. Tests are how you turn “looks right” into “is right” — and they're your safety net for the next step, where we change the implementation.

Turn 4 — Catch the real problem & fix it

Tests pass, but remember Turn 1: the solution uses forbidden built-ins. Now that we have tests, we can safely refactor — the tests tell us instantly if the rewrite breaks anything.

You → Copilot

The assignment forbids bin, len, and strings in this section. Rewrite longest_run using only integer bit operations (&, >>). Keep it O(number of bits). Don't change the tests.

Copilot

Good catch. Pure bit-scan version:

sol1.py — rules-compliant
def longest_run(n):
    """Length of the longest run of consecutive 1 bits in n (n >= 0)."""
    best = 0
    current = 0
    while n:
        if n & 1:                # lowest bit is 1 -> extend the run
            current += 1
            if current > best:
                best = current
        else:                    # hit a 0 -> reset the run
            current = 0
        n >>= 1                   # drop the lowest bit
    return best
✅ Re-run the tests — still all green

Same behavior, now within the rules. This is the whole point of vibe coding: tests let you swap implementations fearlessly. The AI does the typing; the tests keep it honest.

Turn 5 — Keep pushing

You don't stop at “passes”. Keep the conversation going:

🧪 More edge cases

“Add tests for a run that ends at the most-significant bit, and for n with a run at the very start after shifting.”

⚡ Complexity

“What's the time complexity? Confirm it's proportional to the number of bits, not the value of n.”

📖 Readability

“Add type hints and make the docstring state the behavior for n = 0.”

🔀 Alternatives

“Show me a version using the bit-and-shift trick n &= (n << 1) and explain why it counts runs.”

🧭 The loop to remember

Ask (with constraints) → Visualize / understandTestRefactor safelyIterate. You stay the engineer; the AI is a very fast, very literal pair-programmer.


Before you try it: how to talk to the AI

The difference between a frustrating and a magical session is mostly how you ask. Five habits that carry the whole 30 minutes:

🎯 Be specific

“Reverse the low num_bits bits, ignore higher bits” beats “reverse the bits”. Vague prompts get vague code.

🚧 State constraints

Say what's forbidden (no bin, no imports) and required (O(bits), pure integer ops). The AI won't guess the rules.

🔢 Give examples

Paste a couple of input → output pairs. Concrete cases pin down behavior far better than prose.

🪜 Small steps

One change at a time — solve, then “now add tests”, then “now handle num_bits = 0”. Don't ask for everything at once.

🕵️ Read every line — don't trust blindly

AI code is confident, not correct. Before you accept anything, scan for these:

Your job isn't to type less — it's to stay the engineer who decides what's correct.


🏁 Your challenge · reverse_bits(x, num_bits) 30 min

Now you drive. Same loop we just did — ask, visualize, test, improve — but this time you and your assistant. Solve the next Section 1 question.

The task

Write reverse_bits(x, num_bits) that reverses the order of the low num_bits bits of x and returns the result as an integer.

🎯 Your mission (30 minutes)

1. Ask Copilot for a solution — with the constraints.  2. Ask it to visualize / explain a couple of examples.  3. Ask for pytest tests covering the edge cases below.  4. Run them, and if anything's wrong, fix it through the AI.  5. Improve: type hints, docstring, one more edge case.

⏱️ Timer

Time remaining
30:00

🧪 Edge cases to test

CallExpected
reverse_bits(0b1, 4)8 (0b1000)
reverse_bits(0b1011, 4)13 (0b1101)
reverse_bits(0, 8)0
reverse_bits(5, 0)0
reverse_bits(0b111111, 3)7 (ignores high bits)
🎛️ Stuck? Check your answer against this visualizer

Set x and num_bits, and see the low bits get mirrored. Use it to sanity-check your test expectations — not to write the function for you!

Low num_bits bits — before:

↓ reverse ↓

After:

🏆 Done early?

Ask your assistant to also solve next_pow2(n) — the smallest power of two ≥ n — and to explain why its approach avoids floating-point logs (which break for huge integers).