Debugging & Common Mistakes
Every programmer runs into bugs. This page will help you understand Quill's error messages, use the built-in debugger, and avoid the most common pitfalls that trip up new and experienced Quill developers alike.
Reading Error Messages
Quill tries hard to give you helpful error messages. Instead of cryptic codes, you get a pointer to exactly where things went wrong, along with an explanation and often a suggestion for how to fix it.
Anatomy of an error message
Every Quill error has three parts:
- Location — the file name and line number
- Source context — the actual line of code with an underline showing the exact spot
- Hint — a suggestion for how to fix the problem
Let's look at five real error examples you will encounter and learn to read each one.
1. Type mismatch
You have a variable that should hold a number, but you gave it a string:
count is 0
-- ... later in the code ...
count is "hello"
Error [TYPE_MISMATCH] in counter.quill:4
4 | count is "hello"
^^^^^^^
Type mismatch: 'count' was declared as Number but
you are assigning a String.
Hint: Did you mean to assign a number? If you need
a string, declare a new variable instead.
count from its first assignment (0, a number). When you later try to assign a string to it, Quill catches the mismatch. Fix it by assigning a number, or use a different variable for the string value.
Fix
count is 0
-- ... later ...
count is 42
-- Or, if you need a separate string:
label is "hello"
2. Undefined variable
You used a name that hasn't been defined yet:
say greeting
Error [UNDEFINED] in hello.quill:1
1 | say greeting
^^^^^^^^
'greeting' is not defined. You used this name but
never assigned a value to it.
Hint: Did you forget to write: greeting is "..."?
Check for typos in the variable name.
greeting anywhere in scope. This usually means you either forgot to define it, or you have a typo in the name.
Fix
greeting is "Hello, World!"
say greeting
3. Missing colon
You forgot the : at the end of a block statement:
if x is 5
say "five!"
Error [SYNTAX] in check.quill:1
1 | if x is 5
^
Expected ':' after condition. Block statements in
Quill must end with a colon.
Hint: Add a colon: if x is 5:
if, for each, to, while, etc.) need a colon at the end to signal that an indented block follows. This is similar to Python's syntax.
Fix
if x is 5:
say "five!"
4. Wrong indentation
You mixed tabs and spaces, or used an inconsistent indent:
to greet name:
say "Hello, {name}!"
say "Welcome!" -- this line uses a tab!
Error [INDENT] in greet.quill:3
2 | say "Hello, {name}!"
3 | say "Welcome!"
^
Inconsistent indentation. Line 2 uses spaces but
line 3 uses a tab character.
Hint: Use spaces consistently (2 or 4 spaces).
Configure your editor to insert spaces when
you press Tab.
Fix
to greet name:
say "Hello, {name}!"
say "Welcome!"
5. Import not found
The file you're trying to bring in doesn't exist:
bring "./helpers/math"
Error [IMPORT_NOT_FOUND] in app.quill:1
1 | bring "./helpers/math"
^^^^^^^^^^^^^^^^
Could not find module './helpers/math'. Looked for:
- ./helpers/math.quill
- ./helpers/math/index.quill
Hint: Check that the file exists and the path is
correct. Paths are relative to the current file.
.quill extension added automatically) and couldn't find it. Double-check the path and make sure the file exists.
Fix
-- Make sure the file exists at ./helpers/math.quill
bring "./helpers/math"
-- Or check if the path should be different:
bring "./utils/math"
The Debugger
Quill ships with a built-in interactive debugger that lets you pause your program, step through code line by line, and inspect variables. It's one of the most powerful tools you have for finding bugs.
Starting the debugger
Run your file with the debug command instead of run:
quill debug app.quill
You'll see a prompt like this:
Quill Debugger v1.0
Loaded: app.quill (42 lines)
Paused at line 1.
(quill-debug) > _
Setting breakpoints
A breakpoint tells the debugger to pause when it reaches a specific line. Use the break command:
(quill-debug) > break 10
Breakpoint set at line 10.
(quill-debug) > break 25
Breakpoint set at line 25.
To see all your breakpoints:
(quill-debug) > breakpoints
1: line 10 (app.quill)
2: line 25 (app.quill)
To remove a breakpoint:
(quill-debug) > unbreak 10
Breakpoint at line 10 removed.
Stepping through code
| Command | Short | What it does |
|---|---|---|
step |
s |
Execute the current line and move to the next one |
into |
i |
Step into a function call (follow the call) |
out |
o |
Run until the current function returns |
continue |
c |
Run until the next breakpoint (or end of program) |
Inspecting variables
While paused, you can look at the current values of any variable:
(quill-debug) > print count
count = 7 (Number)
(quill-debug) > print name
name = "Alice" (String)
(quill-debug) > print items
items = ["apple", "banana", "cherry"] (List of String)
To see every variable in the current scope at once, use locals:
(quill-debug) > locals
count = 7 (Number)
name = "Alice" (String)
items = ["apple", "banana", "cherry"] (List)
total = 0 (Number)
Viewing the call stack
When you're inside a function, the stack command shows you how you got there:
(quill-debug) > stack
#0 calculateTotal (app.quill:18)
#1 processOrder (app.quill:32)
#2 main (app.quill:45)
This reads from top to bottom: you're currently in calculateTotal at line 18, which was called by processOrder at line 32, which was called from the top level at line 45.
Full walkthrough: finding a bug
Let's debug a function that's returning the wrong result. Here's a program that should calculate the average of a list of numbers, but it's giving the wrong answer:
-- average.quill
to average numbers:
total is 0
for each num in numbers:
total is total + num
give back total / numbers.length
scores are [80, 90, 100]
result is average(scores)
say "Average: {result}"
-- Expected: 90, but getting: 60 ... why?
Let's fire up the debugger and find out:
$ quill debug average.quill
Quill Debugger v1.0
Loaded: average.quill (10 lines)
Paused at line 1.
(quill-debug) > break 5
Breakpoint set at line 5.
(quill-debug) > continue
Hit breakpoint at line 5.
4 | for each num in numbers:
5 | > total is total + num
6 | give back total / numbers.length
(quill-debug) > print num
num = 80 (Number)
(quill-debug) > print total
total = 0 (Number)
(quill-debug) > step
5 | > total is total + num
(quill-debug) > print total
total = 80 (Number)
(quill-debug) > continue
Hit breakpoint at line 5.
(quill-debug) > print num
num = 90 (Number)
(quill-debug) > print total
total = 80 (Number)
(quill-debug) > step
(quill-debug) > print total
total = 170 (Number)
(quill-debug) > continue
Hit breakpoint at line 5.
(quill-debug) > print num
num = 100 (Number)
(quill-debug) > step
(quill-debug) > print total
total = 270 (Number)
(quill-debug) > print numbers.length
numbers.length = 3 (Number)
-- Aha! 270 / 3 = 90. That's correct!
-- Let's continue to see the final output...
(quill-debug) > continue
Average: 90
Program finished.
Common Mistakes & Fixes
Here are the most common mistakes Quill programmers make. Don't feel bad if you run into these — everyone does, especially when learning a new language. Each entry shows the wrong code, the error you'll see, why it's wrong, and how to fix it.
1. Forgetting is for assignment
If you're coming from a language where assignment doesn't need a keyword, you might just write the variable name and value next to each other.
Wrong
name "hello"
Error [SYNTAX] in app.quill:1
1 | name "hello"
^^^^^^^
Unexpected string literal. Did you mean to assign
this value to 'name'?
Hint: Use 'is' for assignment: name is "hello"
Why it's wrong: In Quill, assignment always uses the is keyword. Just writing two things next to each other doesn't mean anything to the compiler.
Fix
name is "hello"
2. Using = instead of is
This is the single most common mistake for anyone coming from JavaScript, Python, or almost any other language. Muscle memory is hard to fight.
Wrong
count = 10
Error [SYNTAX] in app.quill:1
1 | count = 10
^
Unexpected character '='. Quill uses 'is' for
assignment, not '='.
Hint: Write: count is 10
Why it's wrong: Quill was designed to read like English. The = symbol doesn't exist in the language. Use is for assignment and is for comparison too — Quill figures out which you mean from context.
Fix
count is 10
3. Forgetting the colon
Block-starting lines like if, for each, to (function), and while all need a colon at the end.
Wrong
if x is 5
say "five!"
Error [SYNTAX] in app.quill:1
1 | if x is 5
^
Expected ':' after condition.
Hint: Add a colon: if x is 5:
Why it's wrong: The colon tells Quill that an indented block is about to follow. Without it, the parser doesn't know where the condition ends and the body begins.
Fix
if x is 5:
say "five!"
4. Wrong indentation
Code that should be inside a block is placed at the wrong level, or you accidentally de-indented a line.
Wrong
to greet name:
say "Hello, {name}!"
say "Welcome aboard!" -- oops, this is outside the function!
What happens: No error is thrown, but "Welcome aboard!" runs immediately when the file loads, not when greet is called. This is a logic bug, not a syntax error, which makes it harder to spot.
Fix
to greet name:
say "Hello, {name}!"
say "Welcome aboard!" -- now inside the function
editor.renderWhitespace to "all".
5. Using return instead of give back
In most languages you write return, but Quill uses the phrase give back.
Wrong
to add a, b:
return a + b
Error [UNDEFINED] in math.quill:2
2 | return a + b
^^^^^^
'return' is not defined. Did you mean 'give back'?
Hint: Quill uses 'give back' to return values:
give back a + b
Why it's wrong: Quill was designed with English readability in mind. Functions "give back" their results. The keyword return doesn't exist in Quill.
Fix
to add a, b:
give back a + b
6. Comparing with == instead of is
Just like assignment, comparisons use is rather than ==.
Wrong
if name == "Alice":
say "Hi Alice!"
Error [SYNTAX] in app.quill:1
1 | if name == "Alice":
^^
Unexpected '=='. Quill uses 'is' for comparison.
Hint: Write: if name is "Alice":
Why it's wrong: There is no == operator in Quill. The word is handles both assignment and comparison. Quill determines which one you mean based on context: inside an if condition, is means comparison.
Fix
if name is "Alice":
say "Hi Alice!"
7. Forgetting await on async functions
When you call an async function without await, you get a Promise object instead of the actual data.
Wrong
to fetchUser:
response is await fetch("https://api.example.com/user")
give back response.json()
-- Calling without await:
user is fetchUser()
say user.name -- undefined! user is a Promise, not the data
What happens: No error is thrown, but user holds a Promise object rather than the actual user data. Trying to read user.name gives undefined, which can be baffling if you don't know why.
Fix
user is await fetchUser()
say user.name -- works! "Alice"
await inside it, you almost certainly need to await when calling it too. Quill will warn you about this with quill check.
8. Modifying a list while looping over it
Adding or removing items from a list while you're iterating through it leads to unexpected behavior.
Wrong
numbers are [1, 2, 3, 4, 5]
for each num in numbers:
if num is 3:
numbers.remove(num) -- modifying the list mid-loop!
say numbers
-- Expected: [1, 2, 4, 5]
-- Actual: [1, 2, 4] ... it skipped 4!
Why it's wrong: When you remove an item, the loop's internal index gets out of sync with the list's new length. Items get skipped or the loop ends early. This is a classic bug in every language.
Fix
numbers are [1, 2, 3, 4, 5]
-- Build a new list instead of modifying the original:
filtered is numbers.filter(to (num): num is not 3)
say filtered
-- [1, 2, 4, 5]
9. String concatenation with numbers
Trying to glue a string and a number together with + can produce unexpected results.
Wrong
count is 5
say "Count: " + count
Error [TYPE_MISMATCH] in app.quill:2
2 | say "Count: " + count
^
Cannot use '+' with String and Number.
Hint: Use string interpolation instead:
say "Count: {count}"
Why it's wrong: Quill doesn't silently convert types during concatenation (unlike JavaScript). This prevents bugs where "5" + 3 accidentally gives "53". Use string interpolation instead — it's safer and easier to read.
Fix
count is 5
say "Count: {count}"
10. Using my outside a class
The my keyword refers to the current instance and only works inside describe blocks (Quill's classes).
Wrong
-- At the top level of a file:
my.name is "Alice"
Error [CONTEXT] in app.quill:1
1 | my.name is "Alice"
^^
'my' can only be used inside a 'describe' block.
It refers to the current instance of a class.
Hint: If you want a plain variable, just write:
name is "Alice"
Why it's wrong: my is Quill's equivalent of this or self in other languages. It only makes sense inside a class definition where there is an "instance" to refer to.
Fix
-- For a plain variable:
name is "Alice"
-- Or inside a class:
describe Person:
to setup name:
my.name is name -- correct: inside a describe block
11. Forgetting new for class instances
When you create an instance of a class, you need the new keyword.
Wrong
describe Dog:
to setup name:
my.name is name
-- Missing 'new':
buddy is Dog("Buddy")
Error [CALL] in app.quill:6
6 | buddy is Dog("Buddy")
^^^
'Dog' is a class, not a function. Use 'new' to
create an instance.
Hint: Write: buddy is new Dog("Buddy")
Why it's wrong: Without new, Quill tries to call Dog as a regular function, which fails because classes need to be instantiated. The new keyword tells Quill to create a fresh instance and run the setup method.
Fix
buddy is new Dog("Buddy")
say buddy.name -- "Buddy"
12. Circular imports
File A imports file B, and file B imports file A. This creates an infinite loop.
Wrong
-- fileA.quill
bring "./fileB"
to helperA:
say "A"
-- fileB.quill
bring "./fileA" -- circular!
to helperB:
say "B"
Error [CIRCULAR_IMPORT] in fileB.quill:1
Circular dependency detected:
fileA.quill -> fileB.quill -> fileA.quill
Hint: Move shared code into a third file that both
can import, or restructure to remove the cycle.
Why it's wrong: When A loads B, B tries to load A, which tries to load B again, forever. Quill detects this and stops with an error.
Fix
-- shared.quill (new file with shared code)
to sharedHelper:
say "shared"
-- fileA.quill
bring "./shared"
-- fileB.quill
bring "./shared"
13. Off-by-one errors with range
Quill's range function is exclusive of the end value, which surprises many developers.
Wrong (if you expect [1,2,3,4,5])
numbers is range(1, 5)
say numbers
-- You expected: [1, 2, 3, 4, 5]
-- You got: [1, 2, 3, 4]
What happens: No error — the code runs fine, but the result doesn't include 5. This is a logic bug. range(1, 5) produces numbers from 1 up to (but not including) 5.
Fix
-- To include 5, go one higher:
numbers is range(1, 6)
say numbers
-- [1, 2, 3, 4, 5]
range(0, list.length) gives you every valid index without needing to subtract 1.
14. Async in loops
Using await inside a for each loop runs requests one at a time (sequentially), which might not be what you want.
Slow (sequential, one by one)
urls are ["https://api.com/1", "https://api.com/2", "https://api.com/3"]
for each url in urls:
response is await fetch(url) -- waits for each one before starting the next
say response.status
What happens: Each request waits for the previous one to finish before starting. If each takes 1 second, three requests take 3 seconds total. This is correct but slow.
Fix (parallel, all at once)
urls are ["https://api.com/1", "https://api.com/2", "https://api.com/3"]
-- Fire all requests at the same time:
promises are urls.map(to (url): fetch(url))
responses is await Promise.all(promises)
for each response in responses:
say response.status
await inside the loop is the right approach.
15. Mutable state in components
Modifying state directly instead of using state setters means your UI won't update.
Wrong
component Counter:
count is state(0)
to increment:
count is count + 1 -- directly modifying! UI won't update
to render:
give back <button on click is increment>
"{count}"
</button>
What happens: The variable count changes internally, but the component doesn't re-render because Quill's reactivity system doesn't know the state changed. You must use the setter function so Quill can track the update.
Fix
component Counter:
[count, setCount] is state(0)
to increment:
setCount(count + 1) -- use the setter! UI will update
to render:
give back <button on click is increment>
"{count}"
</button>
[value, setter] pattern is how Quill tracks what needs to re-render.
16. Forgetting to handle nothing
A function that doesn't always give back a value will return nothing (Quill's version of null/undefined), which can cause errors downstream.
Wrong
to findUser id:
for each user in users:
if user.id is id:
give back user
-- what if the user isn't found? gives back nothing!
result is findUser(999)
say result.name -- Error: Cannot read 'name' of nothing
Why it's wrong: If no user matches the ID, the function ends without a give back statement, so it returns nothing. Then trying to access .name on nothing crashes.
Fix
to findUser id:
for each user in users:
if user.id is id:
give back user
give back nothing -- explicit!
result is findUser(999)
if result is not nothing:
say result.name
otherwise:
say "User not found"
17. Shadowing a variable by accident
Declaring a variable inside a block with the same name as one outside it. The inner variable "shadows" the outer one, and changes to it don't affect the outer variable.
Wrong
total is 0
to addToTotal amount:
total is amount -- this creates a NEW local 'total', doesn't modify the outer one!
addToTotal(10)
addToTotal(20)
say total -- still 0!
What happens: No error is thrown. The total inside addToTotal is a brand new local variable that disappears when the function ends. The outer total is never touched. This is a silent logic bug.
Fix
total is 0
to addToTotal amount:
change total to total + amount -- explicitly modify the outer variable
addToTotal(10)
addToTotal(20)
say total -- 30
x is value inside a function as a new local declaration by default. To modify a variable from an outer scope, use change x to value. This design prevents accidental mutation of global state.
18. Passing the wrong number of arguments
Calling a function with too many or too few arguments.
Wrong
to greet firstName, lastName:
say "Hello, {firstName} {lastName}!"
-- Too few arguments:
greet("Alice")
Error [ARGS] in app.quill:5
5 | greet("Alice")
^^^^^
'greet' expects 2 arguments (firstName, lastName)
but was called with 1.
Hint: Add the missing argument:
greet("Alice", "Smith")
Why it's wrong: Quill functions are strict about the number of arguments. Unlike JavaScript, missing arguments don't silently become undefined — Quill catches this at compile time.
Fix
greet("Alice", "Smith")
-- Or make the parameter optional with a default value:
to greet firstName, lastName is "":
if lastName is "":
say "Hello, {firstName}!"
otherwise:
say "Hello, {firstName} {lastName}!"
19. Using otherwise if after otherwise
Putting an otherwise if branch after the catch-all otherwise block.
Wrong
if score > 90:
grade is "A"
otherwise:
grade is "F"
otherwise if score > 80: -- this will never run!
grade is "B"
Error [SYNTAX] in grades.quill:5
5 | otherwise if score > 80:
^^^^^^^^^
'otherwise if' cannot appear after 'otherwise'.
The 'otherwise' block must be last.
Hint: Move 'otherwise if' branches before 'otherwise'.
Why it's wrong: The otherwise block is the catch-all default — it must come last. Any branches after it would be unreachable. Quill catches this mistake at compile time.
Fix
if score > 90:
grade is "A"
otherwise if score > 80:
grade is "B"
otherwise:
grade is "F"
Debugging Strategies
Beyond the debugger and error messages, here are proven strategies for tracking down bugs efficiently.
Print debugging with say
The simplest and often most effective technique. Drop say statements into your code to trace what values variables have at different points.
to processOrder items:
say "[DEBUG] processOrder called with {items.length} items"
total is 0
for each item in items:
say "[DEBUG] item: {item.name}, price: {item.price}"
total is total + item.price
say "[DEBUG] total before tax: {total}"
give back total * 1.08
[DEBUG] so you can easily find and remove them later. Some developers use say! (if available) for debug-only output that gets stripped in production builds.
Divide and conquer
When you have a big program and something is wrong, comment out half the code. If the bug disappears, it's in the half you commented out. If the bug remains, it's in the other half. Keep halving until you find it.
-- Comment out sections to isolate the bug:
-- step1()
-- step2()
step3() -- bug appears? it's in step3
-- step4()
This is especially useful for logic bugs that don't produce error messages.
Rubber duck debugging
Explain your code out loud, line by line, to anyone (or anything — a rubber duck works). The act of explaining often reveals the mistake because you have to think about what each line actually does, not what you assume it does.
total but I declared a new total inside the loop! That's the bug!" The act of verbalizing forces you to think carefully.
Reading the compiled JavaScript
Since Quill compiles to JavaScript, you can inspect the output to understand what's actually running:
# Generate the JS output
quill build app.quill
# Read it
cat app.js
This is particularly helpful for understanding how Quill's syntactic sugar translates to actual operations. If the compiled JS looks wrong, you've found a compiler bug (rare, but possible) or a misunderstanding of Quill syntax.
Using the type checker
Quill's check command analyzes your code without running it and catches many errors before they become runtime bugs:
quill check app.quill
app.quill:14 Warning: 'result' might be nothing here.
Add a check before accessing .name
app.quill:22 Error: Cannot call 'push' on a String.
Did you mean to use a List?
Found 1 error and 1 warning.
Run quill check early and often. Many developers add it to their editor's save hook so every file is checked automatically.
Using tests to prevent regressions
Once you've fixed a bug, write a test that would have caught it. This prevents the same bug from coming back.
-- test_average.quill
bring "./average"
test "average of three numbers":
result is average([80, 90, 100])
expect result to be 90
test "average of empty list":
result is average([])
expect result to be 0
test "average of one number":
result is average([42])
expect result to be 42
quill test test_average.quill
See the Testing docs for more on writing and running tests.
Check your assumptions
Most bugs come from wrong assumptions. Before diving deep, verify the basics:
- Is the function even being called? Add a
say "HERE"at the top of the function. - Is the data what you think it is? Print the variable right before the line that's failing.
- Are you editing the right file? It sounds silly, but it happens. Add a unique
saystatement to confirm. - Did you save the file? Some editors don't auto-save. Make sure your changes are on disk before running.
Minimal reproducible example
When a bug is hard to find, try to create the smallest possible program that still shows the problem. Start with your full program and remove things one by one until you have the bare minimum that triggers the bug.
-- Start: 200-line program with a bug
-- Step 1: Remove unrelated functions (150 lines)
-- Step 2: Hardcode inputs (80 lines)
-- Step 3: Remove UI code (30 lines)
-- Step 4: Simplify the data (10 lines)
-- Now you can see the bug clearly!
Compare with working code
If something used to work and now it doesn't, use version control to compare:
# See what changed since the last working version
git diff HEAD~3 app.quill
# Or check out the old version temporarily
git stash
quill run app.quill -- does the old version work?
git stash pop
Often the bug is in something you changed recently. Narrowing down which change broke things is half the battle.
Getting Help
Stuck on something? Here's where to look, in order:
1. Check the error message hints
Quill's error messages almost always include a hint. Read it carefully — it often tells you exactly what to do.
2. Search the docs
The documentation pages cover every part of the language. The Language Reference is especially useful when you're unsure about syntax, and the Standard Library docs list every built-in function.
3. Try the playground
The online playground lets you test small code snippets instantly without setting anything up. It's a great way to verify how a feature works in isolation.
4. Ask on Discord
The Quill community on Discord is active and welcoming. Post your question in the #help channel with:
- The code you're trying to run
- The full error message (or unexpected output)
- What you expected to happen
5. Check the examples directory
The examples directory contains dozens of working programs. If you're trying to do something specific, there's probably an example that shows how. Seeing working code is often more helpful than reading explanations.
Next steps
- Read the Language Reference for a full overview of Quill syntax
- Explore the Standard Library to learn about built-in functions
- Learn about Testing to prevent bugs proactively
- Try the Playground to experiment risk-free
- Check out Examples for working reference code