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.
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.
Here's the classic constant-time bit trick:
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
“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.
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.
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.
Sure — the simplest approach walks the binary string:
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
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.
Before we trust anything, let's see it. Understanding beats guessing.
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.
Here's a small helper that prints each bit and marks the winning run — try it with 0b1110111, 0b101010, and (1<<5)-1:
Type a number and watch the bits. The red cells are the longest run of 1s — exactly what the function should return.
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.
Here you go:
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
python -m pytest -q test_sol1.pyAll 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.
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.
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.
Good catch. Pure bit-scan version:
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
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.
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.”
Ask (with constraints) → Visualize / understand → Test → Refactor safely → Iterate. 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.
AI code is confident, not correct. Before you accept anything, scan for these:
- Invented APIs — functions or arguments that don't actually exist. Run it.
- Plausible-but-wrong logic — off-by-one, wrong edge case, reversed order. This is what tests catch.
- Silent rule-breaking — it used
bin()even though you're in Section 1 (exactly like the demo). - Over-engineering — 40 lines where 5 would do. Ask it to simplify.
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.
- Only the least-significant
num_bitsbits participate; higher bits ofxare ignored. - If
xuses fewer thannum_bitsbits, pad with zeros — e.g.reverse_bits(0b1, 4) == 0b1000 == 8. reverse_bits(x, 0) == 0.- Same rules as Section 1: no
bin, strings, imports — only integer bit operations.
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
🧪 Edge cases to test
| Call | Expected |
|---|---|
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) |
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:
After:
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).