⚡ Promptolis Original · Coding & Development
👨🏫 Code Review Teacher
Not just what's wrong — what senior engineer's brain is pattern-matching to when they smell it.
Why this is epic
Most AI code reviewers tell you what to fix. This one teaches you the mental model a staff engineer uses, so next time you write the code right the first time.
Every finding includes the 'smell' — the pattern the reviewer is recognizing — plus the name of the anti-pattern, the failure mode it leads to in production, and a one-line heuristic you can carry forever.
It ranks findings by 'career impact' — which mistakes would actually cost you credibility in a real PR at a senior company vs. which are nitpicks.
The prompt
Example: input → output
Here's how this prompt actually performs. Real input below, real output from Claude Opus 4.
Language / stack: Python 3.11, FastAPI, Postgres via SQLAlchemy
Author experience level: 2.5 years professional, comfortable with Python and REST APIs, haven't worked much on high-traffic systems
Context: Production endpoint in a B2B SaaS. Handles ~200 req/sec at peak. This is for a billing-adjacent feature.
Intent: Given a customer_id, return their active subscriptions plus the total monthly cost. Called from the customer dashboard on every page load.
Code:
```python
@app.get("/customers/{customer_id}/subscriptions")
def get_subscriptions(customer_id: str, db: Session = Depends(get_db)):
customer = db.query(Customer).filter(Customer.id == customer_id).first()
if not customer:
return {"error": "not found"}
subs = db.query(Subscription).filter(Subscription.customer_id == customer_id).all()
active = []
total = 0
for s in subs:
if s.status == "active":
plan = db.query(Plan).filter(Plan.id == s.plan_id).first()
active.append({
"id": s.id,
"plan_name": plan.name,
"price": plan.price
})
total += plan.price
return {"customer": customer.name, "subscriptions": active, "total": total}
```
Code Review: Customer subscriptions endpoint — billing-adjacent, read-heavy, and currently a latency bomb
Verdict
Request changes. The logic is correct and I can see what you were going for — but this endpoint has an N+1 query, a broken error contract, and it touches money without a single test or type guarantee in sight. At 200 req/sec on a billing page, this will be your most-hit endpoint, and right now it's a pager waiting to buzz. The good news: every problem here is a well-known pattern with a clean fix, and once you internalize them you won't write this shape of code again.
Findings (ranked by career impact)
1. N+1 query in the hot loop — Blocker
The code:
```python
for s in subs:
if s.status == "active":
plan = db.query(Plan).filter(Plan.id == s.plan_id).first()
```
What I'm pattern-matching to: The instant I see a DB call inside a `for` loop over DB results, my stomach drops. This is the single most common performance bug in ORM code and it's the one that makes senior engineers sigh audibly in review. One request → 1 + 1 + N queries. A customer with 12 subs is 14 round trips. At 200 rps, that's ~2800 queries/sec to Postgres for one endpoint.
Anti-pattern name: N+1 query / SELECT-in-loop.
Failure mode in production: Endpoint works fine in staging (small data). Ships. Works fine for most customers. Then your biggest customer — the one with 40 subscriptions — loads their dashboard and takes 4 seconds. Their CSM Slacks you. You add a cache. The cache gets stale. Now billing shows wrong numbers. This is how billing incidents start.
The fix: Join, or use `selectinload`:
```python
subs = (db.query(Subscription)
.options(selectinload(Subscription.plan))
.filter(Subscription.customer_id == customer_id,
Subscription.status == "active")
.all())
```
Also push the `status == "active"` filter into SQL — don't fetch rows just to throw them away.
Heuristic to carry forever: If your ORM call is inside a loop, you've already written a bug. Pull it out or eager-load.
2. Money as a float — Blocker (this is the finance one)
The code:
```python
"price": plan.price
...
total += plan.price
```
What I'm pattern-matching to: You're in a billing-adjacent endpoint and I can't tell from the code whether `plan.price` is a `Decimal`, `float`, or cents-as-int. If it's a float, you are one summation away from returning `29.990000000000002` to a customer. On a billing page.
Anti-pattern name: Floating-point money.
Failure mode in production: Customer sees `$89.97000000001`. Screenshots it. Tweets it. Support ticket. Or worse: you sum prices for invoicing and undercharge by a penny per customer per month, and finance notices in Q3.
The fix: Use `Decimal` end-to-end, or store cents as `int`. Type the response model with Pydantic so the contract is explicit:
```python
class SubscriptionOut(BaseModel):
id: str
plan_name: str
price_cents: int
```
Heuristic to carry forever: Money is never a float. Ever. Not even once.
3. Error contract is a lie — Major
The code:
```python
if not customer:
return {"error": "not found"}
```
What I'm pattern-matching to: Returning a 200 with an `error` key. The client sees HTTP 200 OK and has to introspect the body to know it failed. Every consumer of this API will eventually forget to check, and you'll get silent failures downstream.
Anti-pattern name: Envelope-based errors over HTTP / lying status codes.
Failure mode in production: Frontend shows 'Customer: undefined' because it happily parsed the error object as a customer. Or a retry layer doesn't retry because 200 means success.
The fix: `raise HTTPException(status_code=404, detail="customer not found")`. Let the framework do its job.
Heuristic to carry forever: HTTP status codes exist so clients don't have to read your mind. Use them.
4. No response model, no types on the boundary — Major
What I'm pattern-matching to: FastAPI's entire value proposition is typed boundaries, and this endpoint throws that away by returning a raw dict. Future-you adds a field, forgets to update the frontend, and nothing complains until a user does.
Failure mode in production: Silent schema drift. The API docs (which FastAPI generates from types) say nothing useful. New engineers guess at the shape.
The fix: Define a Pydantic `response_model` on the route. Now the contract is enforced and documented.
Heuristic to carry forever: The boundary of your service is a contract. Make the computer check it.
5. `customer_id: str` with no validation — Minor
If IDs are UUIDs, type them as `UUID` and FastAPI rejects garbage at the door. Saves a DB round trip on every malformed request and gives you a clean 422.
6. Naming: `subs`, `s`, `active` — Nit
In a 15-line function it's fine. I'd still write `subscriptions` and `active_subscriptions` because the cost of typing is zero and the cost of ambiguity compounds. Not worth blocking a PR on.
What You Did Well
- You separated the 'fetch' from the 'transform' mentally, even if the DB calls leaked into the loop. The shape of the function is right.
- You returned a computed `total` on the server instead of making the client sum prices. That's the correct call for money — one source of truth.
- You used dependency injection for the DB session. A lot of 2-year engineers wire sessions globally and regret it.
The One Habit to Build
Treat every endpoint as a contract with a typed request and a typed response, and assume the database is the slowest thing in the room. Those two instincts — 'where are my types?' and 'how many queries is this?' — are 80% of what separates a mid-level backend engineer from a senior one. If you run every PR you write through those two questions before hitting submit, you'll level up faster than any book will take you.
Suggested Practice
Rewrite this endpoint with: (1) a Pydantic request & response model, (2) a single query using `selectinload`, (3) `Decimal` or integer cents for money, (4) proper `HTTPException` for errors, and (5) a pytest test that asserts exactly one SQL query is emitted (use `sqlalchemy.event` or `pytest-postgresql`'s query counter). The last one is the killer exercise — once you've written a test that fails when an N+1 sneaks in, you start seeing them everywhere.
Common use cases
- Leveling up from mid-level to senior by learning what senior reviewers actually look for
- Pre-flighting a PR before humans see it, so you don't burn reviewer goodwill on dumb stuff
- Onboarding to a new language or framework and wanting feedback that teaches idioms
- Learning a codebase style by reviewing your own code against industry norms
- Tech lead training — seeing how to give feedback that educates instead of just corrects
- Interview prep — catching the habits that get flagged in coding interviews
- Reviewing AI-generated code before you commit it, since LLMs produce subtle smells
Best AI model for this
Claude Opus 4.1 or Sonnet 4.5. Opus is noticeably better at seeing architectural smells and explaining WHY a pattern is bad; Sonnet is fine for syntax-level review. Avoid smaller models — they tend to hallucinate pattern names.
Pro tips
- Paste the smallest self-contained unit possible — one function or one file. Teaching quality drops fast when the context is a 2000-line dump.
- Tell it your experience level honestly. 'I'm 2 years in, comfortable with Python, weak on concurrency' produces way better teaching than leaving it blank.
- Specify the context: production service, side project, interview exercise, or prototype. The bar is different and the teacher adjusts.
- If it flags something you disagree with, push back. Ask 'under what conditions would this actually matter?' — it's good at steelmanning both sides.
- Save the heuristics it gives you. After 10 reviews you'll have a personal style guide that's more useful than any book.
- For juniors: ask it to also rewrite one section in the 'senior' style, with inline comments explaining each decision.
Customization tips
- Paste ONE function or ONE file, not a whole repo. Teaching quality is inversely proportional to context size.
- Be honest about your experience level — 'senior but new to Rust' gets very different feedback than '2 years, all Python'. The teacher calibrates.
- Always include the production context. 'Prototype' vs '200 rps production' completely changes which findings are blockers vs nits.
- If you want to learn a specific skill (concurrency, testing, types), say so at the end of the intent field — e.g., 'especially interested in how I'm handling errors'.
- Run it on your own old code from 1–2 years ago. Seeing the smells you've since outgrown is the fastest way to internalize how reviewers think.
Variants
Interview Mode
Reviews as if it's a staff engineer on a coding interview loop — flags things that would cost you the offer vs. nitpicks.
Security Lens
Same teaching format but focused on security smells — auth, input validation, secrets, SSRF, injection — with the attacker's mental model.
Junior-to-Senior Coach
Assumes the author is early-career and explicitly names the 'senior habit' they need to build, with practice exercises.
Explore more Originals
Hand-crafted 2026-grade prompts that actually change how you work.
← All Promptolis Originals