Courses › AI-Assisted Coding › Round Types

Refactoring & Performance with AI

Behavior-preserving refactors in small steps, and measure-profile-fix for slow code.

Refactoring and performance tasks are a natural fit for AI-assisted rounds: "clean up this module", "this endpoint is too slow, fix it". Assistants are good at mechanical transformations. The risk is that they also change behavior while doing it, quietly. What's being tested is whether you can improve code safely, and prove that you did.

Refactoring: protect behavior first

A refactor changes structure without changing behavior. So before touching anything:

  • Run the existing tests. If coverage of the code you're changing is thin, add characterization tests first. See Testing with AI.
  • Name the smells you're fixing, and say why: a 200-line function, duplicated logic in three handlers, a god class, unclear names, deep nesting.
  • Plan small steps, each one leaving the tests green:
  • Extract function, then rename, then move to a module, then remove duplication
  • Commit after each step
In `orders/service.py`, extract the discount calculation in `checkout()` (lines 40–95)
into a pure function `compute_discount(cart, customer) -> int` in a new `orders/pricing.py`.
Behavior must be identical — do not change rounding, ordering, or error handling.
Keep `checkout()`'s public signature. Don't touch tests. Show the diff only.

[!WARNING] Behavior drift Assistants often "improve" things while refactoring: they reorder conditions, change an exception type, swap <= for <, drop a seemingly redundant check, or change the iteration order of a dict or set. Each one can break a caller. Read the diff specifically for semantic changes, and run the tests after each step.

Large mechanical changes

Renames across many files, migrating to a new API, and adding type hints are where agents are most useful. Keep them safe:

  • Ask the agent to make the change in one pass, with no other edits.
  • Check the diff for anything that isn't the mechanical change.
  • Run the type checker or linter as well as the tests.

Performance: measure, don't guess

The workflow interviewers want to see:

  • Reproduce and measure: time the slow path with a realistic input. Write the number down.
  • Profile to find where the time actually goes (cProfile, py-spy, timing logs, the database's query plan). Ask the assistant to help read the profiler output, but look at the hot spots yourself.
  • Form a hypothesis: an N+1 query, O(n²) membership checks on a list, repeated parsing, missing caching, a missing index, synchronous I/O in a loop.
  • Make the smallest fix for the biggest hot spot.
  • Measure again and compare. Run the tests to confirm the behavior didn't change.
# Before: O(n·m)
active = [u for u in users if u.id in banned_ids_list]

# After: O(n + m)
banned = set(banned_ids_list)
active = [u for u in users if u.id in banned]

[!INTERVIEW] Show the numbers "The endpoint took 2.4 s with 10k orders. The profile showed 80% of the time in load_items, one query per order. I batched it into a single query, and it now takes 140 ms, with the same output." That's the whole story interviewers want: measurement, cause, fix, verification.

Common performance fixes to have ready

Knowing when to stop

Stop when the target is met. Further optimization that makes the code harder to read should be proposed, not done: "We could precompute this nightly, but it adds a job to maintain. I'd only do that if traffic grows ten times."

[!TIP] Practice Refactor a Legacy Module with AI · Fix a Slow Endpoint with AI · A Winning Workflow