Skip to main content
← Blog
Pattern Guide10 min read

20 AI Slop Code Examples (With Fixes): Real Slop Code, Named

Twenty patterns, each with the slop version, a cleaner version, and what it costs in review.

Twenty common AI slop patterns in code, each with a cleaner version and what it costs in review. Naming the pattern gives a contributor something to fix, which “this looks like AI slop” does not. The snippets are TypeScript and Python; the patterns appear in any language.

New to the term? Start with what AI slop is. For generated images and filler pages, see AI slop website examples.

Errors hidden behind fallbacks

1. Swallowed exception

Slop code

async function loadInvoices(userId: string): Promise<Invoice[]> {
try {
  return await api.listInvoices(userId);
} catch (error) {
  console.error(error);
  return [];
}
}

Cleaner

async function loadInvoices(userId: string): Promise<Invoice[]> {
try {
  return await api.listInvoices(userId);
} catch (error) {
  throw new Error("loadInvoices failed for user " + userId, { cause: error });
}
}

An empty list reads as “this customer has no invoices”, so an outage is reported as success. Add context and rethrow, or return a typed failure, so the caller can decide. Pattern: swallowed exception.

2. Catch-and-rethrow with no context

Slop code

def read_config(path):
  try:
      with open(path) as f:
          return json.load(f)
  except Exception as e:
      raise e

Cleaner

def read_config(path: Path) -> dict:
  with path.open() as f:
      return json.load(f)

The try block changes nothing, and the broad except Exception invites the next edit to start swallowing errors there. Remove the wrapper, or convert the error once at a real boundary with useful context.

3. Universal fallback

Slop code

async function getPrices(): Promise<Price[]> {
try {
  return await fetchPrices();
} catch {
  return cache.get("prices") ?? DEFAULT_PRICES;
}
}

Cleaner

async function getPrices(): Promise<{ prices: Price[]; stale: boolean }> {
try {
  return { prices: await fetchPrices(), stale: false };
} catch (error) {
  const cached = cache.get("prices");
  if (!(error instanceof NetworkError) || !cached) throw error;
  return { prices: cached, stale: true };
}
}

A 401 or a parsing bug now looks the same as a slow network, and users see old prices with no sign that anything failed. Decide which failures are recoverable and mark degraded data as stale.

4. Retry without a budget

Slop code

def send_webhook(payload):
  while True:
      try:
          return requests.post(WEBHOOK_URL, json=payload)
      except Exception:
          time.sleep(1)

Cleaner

def send_webhook(payload: dict, attempts: int = 4) -> requests.Response:
  for attempt in range(attempts):
      try:
          return requests.post(WEBHOOK_URL, json=payload, timeout=5)
      except (requests.ConnectionError, requests.Timeout):
          if attempt == attempts - 1:
              raise
          time.sleep(2 ** attempt + random.random())

If the endpoint is down for an hour, the worker loops forever with no timeout and every client retries in lockstep. Retry only transient errors, with a cap, backoff and jitter; the receiver still needs an idempotency key if a retried request can arrive twice.

Types and validation

5. Type-system escape hatch

Slop code

const res = await fetch("/api/user");
const user = (await res.json()) as User;
sendEmail(user.email.toLowerCase());

Cleaner

const UserSchema = z.object({ id: z.string(), email: z.string().email() });

const res = await fetch("/api/user");
const user = UserSchema.parse(await res.json());
sendEmail(user.email.toLowerCase());

The cast compiles whatever the payload is, so correctness depends on a server contract the reviewer cannot see. Parse at the boundary instead. Patterns: unsafe type assertion, and the louder double type assertion (as unknown as User).

6. Validate everywhere

Slop code

def create_order(req):
  if not req.get("email") or "@" not in req["email"]:
      raise ValueError("invalid email")
  return service.create(req)

def create(self, req):
  if not req.get("email") or "@" not in req["email"]:
      raise ValueError("invalid email")
  return self.repo.insert(req)

Cleaner

@dataclass(frozen=True)
class OrderRequest:
  email: str

  def __post_init__(self):
      if "@" not in self.email:
          raise ValueError("invalid email")

def create_order(raw: dict) -> Order:
  return service.create(OrderRequest(email=raw.get("email", "")))

When the rule changes, someone has to find every copy, and the copies drift apart without any test noticing. Validate untrusted input once and pass a trusted type through the rest of the code.

7. Defensive check against an impossible state

Slop code

type Status = "active" | "paused";

function label(status: Status): string {
if (!status) return "Unknown";
if (status === "active") return "Active";
if (status === "paused") return "Paused";
return "Unknown";
}

Cleaner

const STATUS_LABELS: Record<Status, string> = {
active: "Active",
paused: "Paused",
};

const label = (status: Status) => STATUS_LABELS[status];

The reader has to prove “Unknown” can never render, and when a third status is added the fallback hides the gap instead of causing a compile error. Trust the type and let a complete lookup table enforce it.

8. Boolean blindness

Slop code

function exportReport(r: Report, pdf: boolean, email: boolean, draft: boolean) {
// ...
}

exportReport(report, true, false, true);

Cleaner

type ExportOptions = {
format: "pdf" | "csv";
delivery: "download" | "email";
draft: boolean;
};

exportReport(report, { format: "pdf", delivery: "download", draft: true });

The call site cannot be checked without opening the signature, and two swapped booleans still type-check. Use named options that only allow valid combinations.

Code that is hard to follow

9. Narrative comment

Slop code

# Initialize the counter
count = 0
# Loop through the users
for user in users:
  # Check if the user is active
  if user.active:
      # Increment the counter
      count += 1

Cleaner

active_users = sum(1 for user in users if user.active)

Narration doubles the diff, and the one comment that matters gets skimmed with the rest. Comment only on constraints or tradeoffs the code cannot show. Patterns: narrative comment, trivial comment.

10. Generic names

Slop code

const data = await getData(id);
const result = data.filter((item) => item.flag);
return handler(result);

Cleaner

const invoices = await fetchInvoices(customerId);
const overdue = invoices.filter((invoice) => invoice.isOverdue);
return sendReminders(overdue);

Names like data, result and handler send the reviewer to other files to learn what three lines do. Name the domain fact instead. Pattern: generic naming.

11. One-call wrapper

Slop code

import { formatDate } from "./dates";

export function formatDateHelper(date: Date, locale: string): string {
return formatDate(date, locale);
}

Cleaner

import { formatDate } from "./dates";

formatDate(order.createdAt, user.locale);

Each wrapper is one more name to learn and one more hop before the code that does something. Call the dependency directly until a wrapper owns some policy or has a second real caller. Pattern: thin wrapper.

12. Oversized orchestration function

Slop code

@app.post("/signup")
def signup(request):
  body = json.loads(request.body)
  if not body.get("email"):
      return error(400, "email required")
  if db.users.find(email=body["email"]):
      return error(409, "exists")
  user = db.users.insert(email=body["email"], plan=body.get("plan", "free"))
  stripe.Customer.create(email=user.email)
  mailer.send(user.email, "Welcome!")
  return {"id": user.id, "email": user.email, "plan": user.plan}

Cleaner

@app.post("/signup")
def signup(request):
  command = SignupCommand.parse(request.body)
  user = register_user(command, users=user_repo, billing=billing)
  return UserResponse.from_user(user).to_json()

Parsing, business rules, persistence, billing, email and response formatting share one block, so the reviewer has to hold all of them at once and none can be tested alone. Keep the handler at the boundary and move each decision into code you can test separately.

13. Duplicate implementation

Slop code

// src/components/InvoiceRow.tsx
function formatMoney(cents: number): string {
return "$" + (cents / 100).toFixed(2);
}

Cleaner

// src/components/InvoiceRow.tsx
import { formatCurrency } from "@/lib/money";

formatCurrency(invoice.totalCents, invoice.currency);

The agent did not search for the existing helper, and the copy drops what the original handles, such as currency and locale. Look for existing code that owns the job before adding more.

14. Dead helper from an abandoned approach

Slop code

import { parseLegacyId } from "./legacy";

function normalizeIdV2(raw: string) {
return raw.trim().toLowerCase();
}

export function lookup(id: string) {
console.log("lookup called", id);
return store.get(id.trim());
}

Cleaner

export function lookup(id: string) {
return store.get(id.trim());
}

The reader assumes normalizeIdV2 matters and goes looking for its caller, and the log line sends user input to production logs. Once tests pass, delete whatever the final solution does not use. Patterns: unused import, console leftover.

Tests that stop testing

15. Test deletion as a fix

Slop code

it.skip("rejects expired coupons", () => {
expect(() => applyCoupon(cart, expiredCoupon)).toThrow(CouponExpiredError);
});

Cleaner

it("rejects expired coupons", () => {
expect(() => applyCoupon(cart, expiredCoupon)).toThrow(CouponExpiredError);
});

// src/coupons.ts: fix the implementation, not the test
if (coupon.expiresAt <= now) throw new CouponExpiredError(coupon.code);

CI goes green on a one-token diff, and the regression ships unless someone notices .skip. Fix the implementation, or explain the changed contract and replace the test with a stronger one.

16. Tautological generated test

Slop code

def test_order_total():
  items = [Item(price=10, qty=2), Item(price=5, qty=1)]
  expected = sum(i.price * i.qty for i in items) * (1 + TAX_RATE)
  assert order_total(items) == expected

Cleaner

def test_order_total_includes_tax():
  items = [Item(price=10, qty=2), Item(price=5, qty=1)]
  assert order_total(items, tax_rate=0.2) == pytest.approx(30.0)

If the formula is wrong, the test is wrong the same way, so coverage rises while the test proves nothing. Assert a result you know independently, at the public boundary.

17. Snapshot everything

Slop code

it("renders checkout", () => {
const { container } = render(<Checkout cart={cart} />);
expect(container).toMatchSnapshot();
});

Cleaner

it("shows the discounted total", () => {
render(<Checkout cart={cartWithCoupon} />);
expect(screen.getByRole("status")).toHaveTextContent("Total: $18.00");
});

A snapshot diff of several hundred lines gets approved by scrolling, with the one changed price somewhere inside. Assert the behavior you care about and review intended snapshot changes separately.

Dependencies, configuration and scope

18. Hallucinated dependency

Slop code

// src/db/pool.ts, written by the agent
import { createPool } from "pg-promise-x";

export const pool = createPool(process.env.DATABASE_URL);

Cleaner

// Check before adding: does it exist, who publishes it, is it needed?
// npm view <name> repository.url maintainers time.created license
import { Pool } from "pg"; // already in package.json

export const pool = new Pool({ connectionString: process.env.DATABASE_URL });

A nonexistent or typo-squatted package name looks like a real one in a diff. Before installing, check the registry, repository, maintainers, age, license and whether you need it at all.

19. Speculative configuration

Slop code

export const cacheConfig = {
ttl: Number(process.env.CACHE_TTL ?? 300),
strategy: process.env.CACHE_STRATEGY ?? "lru",
enableSharding: process.env.ENABLE_SHARDING === "true",
maxShards: Number(process.env.MAX_SHARDS ?? 16),
retryMode: process.env.CACHE_RETRY_MODE ?? "exponential",
};

Cleaner

export const CACHE_TTL_SECONDS = 300;

Each option is an untested code path that an operator can still switch on. Ship the smallest configuration the task needs and add options when a real second case appears.

20. Drive-by rewrite

Slop diff

-export function dueDate(invoice: Invoice) {
-  return addDays(invoice.issuedAt, invoice.terms);
-}
+export const computeInvoiceDueDate = (inv: InvoiceRecord): Date => {
+  const { issuedAt, terms } = inv;
+  return addBusinessDays(issuedAt, terms ?? DEFAULT_TERMS);
+};

Cleaner diff

 export function dueDate(invoice: Invoice) {
-  return addDays(invoice.issuedAt, invoice.terms);
+  return addDays(invoice.issuedAt, invoice.terms ?? DEFAULT_TERMS);
}

The one-line fix hides inside a rename, a type change and an unrequested switch to business days, and every caller of dueDate is now in the diff. Keep the bug fix small and move cleanup to a separate change.

Who should check each example

Patterns with a fixed shape or a metric can be automated. Contextual ones suit an AI reviewer, as questions to verify. Intent, risk and scope stay with a person.

  • Automate: swallowed exceptions (1), unsafe casts (5), narrative comments (9), generic names (10), thin wrappers (11), oversized functions (12), dead imports and debug leftovers (14).
  • Ask an AI reviewer, then verify: universal fallbacks (3), unbounded retries (4), duplicated validation (6), impossible-state checks (7), duplicate implementations (13), tautological tests (16).
  • Keep with a human owner: skipped tests (15), snapshot updates (17), new dependencies (18), new configuration (19), and scope (20).

aislop detects many of the fixed-shape ones, including swallowed errors, unsafe type assertions, narrative comments, dead code, empty functions, debug leftovers, unresolved stubs, generic naming and complexity thresholds, with the same rule ID whoever wrote the code.

npx aislop@latest scan --changes

Run it in your repository to see which of these 20 are already there; it needs no install and no account. Each pattern is documented in the pattern library, and for a public GitHub repository you can try aislop in the browser.

Sources and further reading

Frequently asked questions

What are AI slop code examples?

Concrete snippets of low-value code that coding agents tend to leave behind, such as swallowed exceptions, narrative comments, unsafe casts, fake fallbacks, hallucinated dependencies and disabled tests, shown next to a cleaner version.

What does AI slop look like in code?

Mostly code that runs but hides failures or adds noise: swallowed errors, redundant comments, unsafe casts, dead helpers, generic names, skipped tests, oversized functions and unrelated edits left from the agent's search.

Are AI slop website examples the same as slop code?

No. Website slop (generated images, filler landing pages, template copy) is covered in AI slop website examples at /blog/ai-slop-website-examples/; this page covers code in a repository.

Is every code smell AI slop?

No. Humans write the same patterns, so review the code and its impact rather than guessing who wrote it.

Can AI slop be detected automatically?

Many repeatable patterns can, such as empty catches, unsafe assertions, dead code, debug statements, placeholders, trivial comments, large functions and vulnerable dependencies. Wrong intent and plausible but unneeded changes still need human review.

All posts