cd /news/artificial-intelligence/we-asked-if-vibecoding-had-fried-peo… · home topics artificial-intelligence article
[ARTICLE · art-124444] src=codeset.ai ↗ pub= topic=artificial-intelligence verified=true sentiment=· neutral

We asked if vibecoding had fried people's brains

A quiz created by Codeset founder Nuno Saavedra to test whether 'vibecoding' — relying on AI to write code — had eroded programmers' fundamentals drew 5,783 completions and found most respondents scored in the middle range, with a mean score of 4.97 out of 10 and a median of 5. Only 12% scored in the 'Fried' range (0–2), while 31% landed in 'Mush' (3–4), 32% in 'Soft' (5–6), 19% in 'Toasted' (7–8), and 5% in 'Intact' (9–10), with 86 people achieving a perfect score. The quiz, hosted at codeset.ai/fried, revealed that respondents missed 54% of ordinary language questions and 42% of model-framed items, with common errors centering on Python's mutable default arguments and setdefault behavior.

by read7 min views2 publishedSep 9, 2026

Nuno Saavedra7 min read

We wanted a meme. The bit was that AI had written enough of everyone’s code that the fundamentals were gone. Has vibecoding fried your brain?

So we made a quiz. We gave it funny characters so it looked like a personality test, and a roast at the end so each person walked away with something of their own to share. We put it at codeset.ai/fried and waited for the joke to land.

It took off. A LinkedIn post did the work: 120k+ views, and 5,783 people finished the quiz.

What started as a joke had a result in it, but not the result the title promised.

Most people were not fried #

The mean score is 4.97 out of 10. The median is 5. Eighty-six people got a perfect score.

If the meme were true, this chart would pile up on the left. It doesn’t. It peaks at 4 and 5, which is somewhat of a relief... at least for now.

Verdict Score People Share
Fried 0–2 718 12%
Mush 3–4 1,789 31%
Soft 5–6 1,871 32%
Toasted 7–8 1,097 19%
Intact 9–10 308 5%

Fried and Intact are the characters you share as a bit, however most people got Mush or Soft.

Each quiz mixed ordinary language questions with a few items framed as something a model wrote. People missed both: 54% of the ordinary items, 42% of the model ones.

Same quiz, different shapes #

Python was the default choice on the quiz, so most people took it: 3,767 finishes, against a few hundred each for JavaScript, Java, and C, and 160 for Rust.

However, the interesting part is not the counts but where the scores sat.

JavaScript, Java, and C look like the same quiz: a bump around 5, means within a tenth of a point of each other. Python, the crowd that never changed the picker, sits a little to the left and Rust sits clearly to the right.

That does not mean Python programmers are worse, or that Rust programmers are better, even though the Rust community will probably still say they are. Python is who showed up. Rust is who chose Rust. The comparison that survives is the middle three: different languages, same shape.

What follows is mostly Python, because that is the sample large enough to look at individual questions. JavaScript shows up when the same idea appears there too.

The same few ideas, missed over and over #

The title said fried brains. The questions said something more specific.

They still picture a fresh list

def add(item, bag=[]):
    bag.append(item)
    return bag

Two items in the quiz used this trap. One asked what add(1) then add(2) return. The other showed the same function with two prints, said the model claimed it prints [1] then [2], and asked what it actually prints.

The answer to both is [1] then [1, 2]. The [] in bag=[] is not rebuilt on each call. Python creates that list once, when the function is defined, and every call appends to the same object.

About six in ten missed the first question. About half missed the second. On the first, 583 of 676 wrong answers were [1] then [2]: people assumed each call started with a fresh, empty list. They knew what append did; what they did not picture was the default argument as one actual list, created once, attached to the function, and quietly reused on every call.

I (Nuno) would have been in that pile. I hit this exact bug in a university project: a list kept growing between calls I was sure were independent, and it took me far too long to realize the default argument was the culprit.

A third item tested the same mental model from a different angle:

d = {}
a = d.setdefault("items", [])
b = d.setdefault("items", [])
b.append("x")
print(a, b)

setdefault returns the value already stored under the key, so a and b point to the same list and the output is ['x'] ['x']. About 56% missed it. The most common wrong answer was [] ['x']: the same instinct as before, treating each expression as if it produced a fresh list rather than another reference to an existing one.

They don't know what sort does

nums = [3, 1, 2]
print(nums.sort())

About 59% missed it. 495 people answered [1, 2, 3]. The trap is that sort() does two separate things: it changes nums in place, and it returns None. So after the call, nums is [1, 2, 3], but print(nums.sort()) prints None.

JavaScript has a related trap:

console.log([10, 2, 1].sort());

About 68% missed it. Most answered [1, 2, 10]. Here sort() does return the array, but JavaScript sorts elements as strings by default, not numerically. It compares "1", "10", and "2", so the result is:

[1, 10, 2]

Both questions expose the same habit: seeing a familiar method name and jumping straight to the result we expect, without checking exactly what the method returns or how it performs the operation.

Assignment makes a name local

The hardest identifiable Python item is not a meme.

def f():
    x = 1
    def g():
        print(x)
        x = 2
    g()
f()

What happens when f() runs?

About 83% missed it. 292 of 395 misses said “Prints 1.” That answer assumes g() reads the x from f() and only later creates its own.

Because g() assigns to x anywhere in its body, Python treats x as a local variable throughout all of g(). So print(x) tries to read that local variable before it has been assigned.

The result is an UnboundLocalError. The later x = 2 changes what the earlier print(x) means.

A generator is not a list

def split_positive(values):
    it = (x for x in values if x > 0)
    has_any = any(it)
    return has_any, list(it)

Which input makes this misbehave, if the returned list should contain every positive?

About 68% missed it. The most popular wrong answer was [0, 0]. The input that actually breaks the function is [0, 4]: any() advances the iterator until it finds the truthy 4, so by the time list(it) runs, the iterator is already exhausted.

That was the pattern across the iterator questions. With all() on a generator, about half assumed every value would still be checked even after a false one appeared. With zip(it, it) on an odd-length iterator, many expected the leftover element to remain untouched. In each case, the missing mental model was the same: iterators have state. If you consume them, they move; if you walk them to the end, they are gone.

d.get is not d[key]

class Buckets(dict):
    def __missing__(self, key):
        value = []
        self[key] = value
        return value

d = Buckets()

d.get("items", []).append("x")
print(d)  # {}

d["items"].append("x")
print(d)  # {'items': ['x']}

A dict subclass can define __missing__ to create a value on demand, but that hook is only used by d[key]. d.get(key, []) skips it entirely — about 64% thought it would still call the hook. If you want the bucket to be created before you mutate it, you need d[key], not get.

The same protocol detail explains another trap: if __missing__ does return self[key], it just triggers itself again and again, ending in a RecursionError, not a KeyError.

The gap here is not really about defaults. Most people already understand that settings.get("retries", 1 / 0) can still raise even when "retries" exists, because the default expression is evaluated before get is called.

The real gap is the lookup protocol. get looks like the safer form of dictionary access, but precisely because it is a different lookup path, it bypasses __missing__.

So did it fry anyone? #

Not in the way the joke wanted. The average person who clicked the meme got five out of ten. They catch a lying comment and an empty except, but they still picture a fresh default list, a sorted array coming back from sort, and a generator you can walk twice.

That is less “brains fried” and more “these objects do not do what the English suggests”.

The quiz is still up. codeset.ai/fried

── more in #artificial-intelligence 4 stories · sorted by recency
── more on @codeset 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
Live at https://your-agent.zahid.host
Get free account → Pricing
from €0/mo · no card required
LIVE [news/we-asked-if-vibecodi…] indexed:0 read:7min 2026-09-09 ·