Free tool · Code
Code explainer
Paste code in any language and get it explained in plain English, with what it returns on an example and the bugs and edge cases in it: 1 free run a day on GPT-6 Luna, no account needed. We checked every model's explanations against the code's real output.
1 free run a day, no account needed
GPT-6 LunaBy running it, you agree to the Terms and Privacy Policy and confirm you’re 18 or older. We keep what you send and its answer for 7 days, or in your account if you sign up.
Test runs checked . Updated .
Code explainer: three examples
Three inputs and the explanation a free run gave each, made once on September 29, 2026 with GPT-6 Luna and shown as it came back. The example buttons in the box above show the same.
const isEmail = (s) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(s);
This is a one-line JavaScript function that checks whether a value matches a simple email-shaped pattern. It returns `true` or `false`; it does not print anything.
It runs a regular-expression test on `s`. The pattern requires:
- at least one character other than whitespace or `@` before an `@`;
- at least one character other than whitespace or `@` between the `@` and a dot;
- at least one character other than whitespace or `@` after the dot.
For example, `isEmail("alex@example.com")` returns `true`. `isEmail("alex@example")` returns `false` because there is no dot after the `@`.
This is only a rough format check, not full email validation. For example, `isEmail("a@b.c")` returns `true`, even though many systems would reject such a short domain. It also accepts some questionable addresses, such as `isEmail("a..b@example.com")`, which returns `true`.
A notable edge case: passing a non-string can behave unexpectedly. For example, `isEmail(null)` throws a `TypeError` because the regular expression’s `.test()` method tries to convert `null` to a string, actually yielding `"null"`—so this call returns `false`, not an error. More generally, `.test()` coerces many non-string values to strings: `isEmail(123)` returns `false`, while an object with a custom string conversion could match.SELECT c.name, SUM(o.total) AS spent FROM customers c LEFT JOIN orders o ON o.customer_id = c.id GROUP BY c.name ORDER BY spent DESC LIMIT 5;
This is a SQL query. It lists up to five customer names with the sum of their order totals, sorted from highest spending to lowest. It runs in this order: 1. `FROM customers c` starts with the `customers` table, using `c` as a short alias. 2. `LEFT JOIN orders o ON o.customer_id = c.id` attaches matching orders to each customer. Because it is a left join, customers without orders are still included. 3. `GROUP BY c.name` combines rows by customer name so the query can calculate one total per name. 4. `SUM(o.total) AS spent` adds each group’s order totals and names the result `spent`. 5. `ORDER BY spent DESC` sorts the results by spending, greatest first. 6. `LIMIT 5` keeps only the first five rows. For example, if Alice’s orders total 30 and 20, Bob’s total 40, and Cara has no orders, the query returns rows like: ```text Alice | 50 Bob | 40 Cara | NULL ``` That example has fewer than five rows, so all are shown. SQL `SUM` ignores `NULL` values; for a customer with no matching orders, the sum is `NULL`, not zero. Potential surprises and bugs: - **Customers with the same name are combined.** If two different customers are both named “Alex,” `GROUP BY c.name` merges their orders into one total. This happens whenever `customers` contains duplicate names. Grouping by the customer’s unique ID as well as the name would keep them separate. - **Customers with no orders get `NULL` for `spent`.** This happens when a customer has no matching rows in `orders`; use `COALESCE(SUM(o.total), 0)` if they should show as zero. - **Ties can make the top five unpredictable.** If customers tie at the fifth position, the query has no secondary sort rule, so which tied customer appears can vary. - **A `NULL` customer name forms a group too.** Any customer whose `name` is `NULL` is grouped together with other customers whose name is `NULL`.
def chunks(items, n): return [items[i:i + n] for i in range(0, len(items), n)]
This is Python code that divides a list-like value into smaller pieces, each containing up to `n` items. It returns those pieces as a new list. The function `chunks` takes two inputs: `items`, the collection to split, and `n`, the size of each piece. It uses a *slice*—a way to take part of a sequence—to collect items from position `i` up to, but not including, position `i + n`. The `range(...)` supplies the starting positions, moving forward by `n` each time. For example, `chunks([1, 2, 3, 4, 5], 2)` returns `[[1, 2], [3, 4], [5]]`. The last piece can be shorter than `n`. Surprising behavior and edge cases: - If `items` is empty, as in `chunks([], 2)`, the result is `[]`. - If `n` is larger than the number of items, as in `chunks([1, 2], 5)`, the result is `[[1, 2]]`. - If `n` is `0`, as in `chunks([1, 2], 0)`, Python raises a `ValueError` because `range` cannot use a step of zero. - If `n` is negative, as in `chunks([1, 2, 3], -1)`, the result is `[]`: the range starts at `0` and cannot move forward by a negative step toward its stop value. - `items` must support `len(...)` and slicing, as lists and strings do. For example, passing a generator raises a `TypeError` because generators do not support those operations.
Which AI model is best at this? We tested every one
In our test runs of 5 Code explainer inputs on 19 models, GPT-6 Luna, GLM 5.3 Flash, Claude Haiku 5.5 and 15 more passed the most: 5 of 5. That includes GPT-6 Luna, the model a free run is on.
Run on October 9, 2026: 5 inputs on 19 models, each with this tool's own instructions.
| Model | Passed | Hard ones | Messages used on Pro | Cost per run | Time per run |
|---|---|---|---|---|---|
| GPT-6 LunaOpenAIThe free run's model | 5 of 5 | 2 of 2 | 1 each, of 60 a day on Pro | $0.0001 | 10.4 s |
| GLM 5.3 FlashZ.ai | 5 of 5 | 2 of 2 | 1 each, of 60 a day on Pro | $0.0003 | 8.2 s |
| Claude Haiku 5.5Anthropic | 5 of 5 | 2 of 2 | 1 each, of 60 a day on Pro | $0.0004 | 4.4 s |
| DeepSeek V4.1 FlashDeepSeek | 5 of 5 | 2 of 2 | 1 each, of 60 a day on Pro | $0.0019 | 4.9 s |
| Gemini 3.8 FlashGoogle | 5 of 5 | 2 of 2 | 1 each, of 250 a month on Pro | $0.0015 | 3.8 s |
| GLM 5.3Z.ai | 5 of 5 | 2 of 2 | 1 each, of 250 a month on Pro | $0.0024 | 2.9 s |
| Mistral Large 4Mistral | 5 of 5 | 2 of 2 | 1 each, of 250 a month on Pro | $0.0063 | 30.2 s |
| DeepSeek V4 ProDeepSeek | 5 of 5 | 2 of 2 | 1 each, of 250 a month on Pro | $0.0089 | 14.0 s |
| Grok 4.7xAI | 5 of 5 | 2 of 2 | 1 each, of 250 a month on Pro | $0.0100 | 19.0 s |
| GPT-6 SolOpenAI | 5 of 5 | 2 of 2 | 1 each, of 125 a month on Pro | $0.0024 | 4.6 s |
| GPT-6.1 SolOpenAI | 5 of 5 | 2 of 2 | 1 each, of 125 a month on Pro | $0.0027 | 5.9 s |
| Kimi K3Moonshot | 5 of 5 | 2 of 2 | 1 each, of 125 a month on Pro | $0.0059 | 12.2 s |
| Gemini 3.1 Pro (preview)Google | 5 of 5 | 2 of 2 | 1 each, of 125 a month on Pro | $0.0089 | 12.1 s |
| Claude Sonnet 5.5Anthropic | 5 of 5 | 2 of 2 | 1 each, of 125 a month on Pro | $0.0101 | 7.0 s |
| Claude Sonnet 5Anthropic | 5 of 5 | 2 of 2 | 1 each, of 125 a month on Pro | $0.0107 | 10.1 s |
| Claude Opus 5.5Anthropic | 5 of 5 | 2 of 2 | 1 each, of 62 a month on Pro | $0.0296 | 13.5 s |
| GPT-6 AstraOpenAI | 5 of 5 | 2 of 2 | 1 each, of 31 a month on Pro | $0.0139 | 7.2 s |
| Claude Fable 5.1Anthropic | 5 of 5 | 2 of 2 | 1 each, of 31 a month on Pro | $0.0485 | 11.6 s |
| Claude Haiku 4.5Anthropic | 4 of 5 | 1 of 2 | 1 each, of 250 a month on Pro | $0.0023 | 4.5 s |
Side by side: what a mutable default prints
The same input on GPT-6 Luna and GLM 5.3 Flash, as each answered it, unedited.
Explain this code in plain English: what it does, what it returns on an example, and any bugs or surprising behaviour, with the input that triggers each.
---
What does this print, and why?
def add_tag(tag, tags=[]):
tags.append(tag)
return tags
print(add_tag("a"))
print(add_tag("b"))This is Python code that defines `add_tag`, which appends a tag to a list and returns that list. Because `tags=[]` is created once when the function is defined, calls that omit `tags` share the same list. The first call appends `"a"` and prints: ```text ['a'] ``` The second call appends `"b"` to that same list, so it prints: ```text ['a', 'b'] ``` This shared default list is surprising if each call was meant to start with a fresh list. It happens specifically when `add_tag` is called without a `tags` argument more than once; passing a list explicitly uses the list you passed.
# What this code is
This is **Python**. It defines a function that appends a tag to a list and returns that list. It looks like it should print two separate one-item lists — but it doesn't.
# What it actually does
```python
def add_tag(tag, tags=[]):
tags.append(tag)
return tags
```
The default value `tags=[]` is created **once, when the function is defined** — not fresh each time it's called. Python stores that single list on the function object and reuses it for every call where no `tags` argument is passed.
So both calls mutate and return the *same* list:
1. `add_tag("a")` → appends `"a"` to the shared default list → prints `['a']`
2. `add_tag("b")` → appends `"b"` to that **same** list → prints `['a', 'b']`
Output:
```
['a']
['a', 'b']
```
This is the classic "mutable default argument" gotcha in Python. (You can see the shared object with `add_tag.__defaults__`, which will show `(['a', 'b'],)`.)
# Example with explicit input
If you pass your own list, it behaves as expected and doesn't touch the shared one:
- `add_tag("x", [])` → returns `['x']`, and a later `add_tag("y")` still returns `['a', 'b', 'y']`.
Wait — that last detail is the surprise: the default list has already accumulated `"a"` and `"b"` from the earlier calls, so `print(add_tag("y"))` after those two prints prints `['a', 'b', 'y']`, not `['y']`.
# Bugs and edge cases
- **Shared state between calls**: any call without `tags` appends to one global-ish list. Trigger: two or more calls like `add_tag("a")` then `add_tag("b")`.
- **Mutating the caller's list**: if you pass a list, the function modifies *your* list in place and returns the same object — `my = []; add_tag("z", my)` changes `my` to `['z']`. Some callers expect a new list back.
- **`None` input**: `add_tag("t", None)` raises `AttributeError: 'NoneType' object has no attribute 'append'`.
# The standard fix
Use `None` as the sentinel:
```python
def add_tag(tag, tags=None):
if tags is None:
tags = []
tags.append(tag)
return tags
```
With that version, both calls print `['a']` and `['b']` as most people expect. (Note the fix still mutates a list you pass in; if you want a pure function, build a copy instead.)How the inputs are scored
- Key facts: the answer must state each fact the input turns on (a number, a name, a date or a condition), as the input gives it, keep any format rule the input sets (one sentence, a word limit, a number of items, a length for each), and say nothing the input rules out (an ingredient, a claim it doesn't back); how it's worded isn't judged.
The inputs: a python loop that removes duplicates, a title-case function with a bug, a sql query with having, what a mutable default prints, and javascript's sort on numbers. Every model gets the tool's own instructions and up to Pro's reply limit. The method is the same as our other test runs.
How to use the Code explainer
Paste the code, and your question about it if you have one (“why does this return None?”). Up to 1,500 characters.
Pick how much you already know.
Press “Explain it”. Without an account you see the start (1 free run a day, no account needed).
Run the example it gives to check the explanation against the real output.
Tips for a better answer
Ask your real question
Ask the question you actually have (“why is the last item missing?”). A focused answer beats a tour of every line.
Include the error
Paste the error message or the wrong output with the code. The explanation starts from what went wrong.
Set the level
Pick Beginner to have every term defined, or Experienced to skip straight to the bugs and the odd parts.
Then fix or convert it
Signed in, follow up with “now fix it” or “convert it to Python” in the same chat. It keeps the explanation in view.
What a good explanation looks like
What it returns, on an example
A good explanation says what goes in and what comes out, with one concrete example, before it walks through the lines. If you still can't predict the output, it hasn't explained anything.
The edge cases, named
Ask what happens on an empty input, a negative number or a missing key. The explanation should say whether the code handles it or breaks.
The surprises, caught
Some code does something other than what it looks like: JavaScript's sort() compares numbers as text, a Python default list is shared between calls. An explainer that misses these hasn't read the code.
Check it by running it
When the explanation says what the code prints, run it and check. An explanation can sound certain and still get the output wrong; the results below show how often each model did.
What's free, and what signing up adds
Without an account: 1 free run a day, on GPT-6 Luna, shared with the free message on our home page. It takes up to 1,500 characters, writes up to about 600 tokens (roughly 450 words), and shows you the start of the explanation.
Signing up is free, with an email link and no card. llmwise accounts are for people 18 and older. The explanation you ran comes with you as your first chat, and you get 5 free messages on almost every model (all but Claude Fable 5.1 and GPT-6 Astra).
Signed in, paste up to 20,000 characters in a run. It runs on the model you pick, on your plan, shows the whole answer, and you can carry on in the chat with any model.
More free tools are on the tools page, and what each plan gives is on pricing.
Questions
Is the code explainer free?
Yes. Without an account you get 1 run a day on GPT-6 Luna, shared with the free message on our home page: you see the start of the explanation, and the rest when you sign up. Signing up is free and adds 5 free messages on almost every model in llmwise (all but Claude Fable 5.1 and GPT-6 Astra; one of them can be on Claude Opus 5.5), with no card.
Which languages can it explain?
Any common one: Python, JavaScript, TypeScript, Java, C, C++, C#, Go, Rust, PHP, Ruby, SQL, shell scripts and regular expressions. It names the language it found.
Will it find bugs?
Yes: it says what looks wrong, which input breaks it, and how to fix it. It reads the code rather than running it, so run the case it names to confirm.
How do you know the explanations are right?
We ran five snippets on every model and checked each explanation for facts we'd established by running the code: what it prints, where it throws, and what it does. The results are below.
Can I paste a whole file?
Signed in, paste up to 20,000 characters on any of 19 models and ask follow-ups in the same chat (“why that line?”, “rewrite it without the bug”). Don't paste keys, passwords or customer data into any AI tool.
Which AI is best for this, in your tests?
In our test runs of 5 Code explainer inputs on 19 models, GPT-6 Luna, GLM 5.3 Flash, Claude Haiku 5.5 and 15 more passed the most: 5 of 5. That includes GPT-6 Luna, the model a free run is on. The table on this page has every model's result, what a run counts as on Pro, and the time it took.
Claude, GPT, Gemini, DeepSeek, Grok, Kimi, GLM, and Mistral, in one chat.
See what a message costs before you send it. Free is 5 messages to try; sign in with an email link, no password or card.