Skip to content
Aback Games

Common coding bugs and how to spot them

Off-by-one loops, NaN and NULL, falsy zero, shared defaults, sorting surprises and forgotten awaits: what goes wrong and the habit that catches each one.

Updated 4 min readBy the Aback Games team

Most bugs are not clever. They are the same few slips, made in every language, that look right at a glance and quietly give the wrong answer. Learning to recognise them is one of the fastest ways to get better at reviewing and debugging code.

Each bug below is one you can practise spotting in Bug Hunt, which shows short JavaScript, Python and SQL snippets with one broken line each.

Off by one

A loop that runs once too often or once too few is the classic bug. In JavaScript, for (let i = 0; i <= items.length; i++) reads one past the end, where items[i] is undefined. In Python, range(1, 10) stops at 9, and the slice items[-3:-1] leaves out the last item.

Habit: check the first and last pass of every loop, and remember that ranges and slices stop before their end value.

Comparisons that never succeed

Some values are not equal to anything, including themselves. In JavaScript, x === NaN is always false; use Number.isNaN(x). In SQL, phone = NULL is never true; use phone IS NULL. In Python, is checks for the same object, so role is "admin" can be false even when the text is equal; use ==.

Floating-point numbers belong here too: 0.1 + 0.2 == 0.3 is false in nearly every language. Compare floats with a tolerance, such as Python’s math.isclose.

Zero is falsy

In JavaScript, level || 50 falls back to 50 when level is 0, because 0 is falsy. The same trap catches empty strings. Use level ?? 50, which falls back only for null and undefined.

A relative of this bug: if (list.indexOf(x)) is false when x is the first item (index 0) and true when it is missing (-1). Use includes.

Values that are shared when they look copied

  • Python creates a default argument once. def add(tag, tags=[]) reuses the same list on every call; default to None and make the list inside.
  • [[0] * 3] * 3 in Python repeats one inner list three times, so setting one cell sets a whole column.
  • A JavaScript spread, { ...defaults }, copies only the top level. Nested arrays and objects are still shared.

Habit: whenever data is copied, ask whether anything inside it is a list or an object.

Functions that do not return what you think

Python’s list.sort() sorts in place and returns None; sorted() returns a new list. Strings never change, so text.upper() on its own line does nothing useful. In JavaScript, sort() without a compare function sorts numbers as text, putting 100 before 25.

["10", "10", "10"].map(parseInt) gives [10, NaN, 2], because map passes the index as parseInt’s second argument, the radix.

Async code that is not awaited

items.forEach(async (item) => await save(item)) does not wait for anything: forEach ignores the promises, so the code after it runs at once. Use for...of with await inside, or await Promise.all(items.map(save)).

Closures that see the final value

A function made inside a loop sees the loop variable as it is when the function runs, not when it was made. With var in JavaScript, three timeouts all log 3; let gives each pass its own variable. Python lambdas behave the same way, so lambda x: x * n in a loop uses the last n; bind it with a default, lambda x, n=n: x * n.

SQL that drops rows

  • A condition on the right-hand table of a LEFT JOIN, written in WHERE, removes the rows with no match. Put it in the ON clause.
  • NOT IN (subquery) returns nothing if the subquery yields a NULL.
  • COUNT(column) skips NULLs; COUNT(*) counts every row.
  • BETWEEN with a date on a timestamp column stops at midnight at the start of the last day.
  • AND binds tighter than OR, so mixed conditions need parentheses.

How to get faster at spotting bugs

Read the goal first, then look for the input that would break the code: an empty list, zero, NULL, the last item. Check every comparison operator and every boundary. And practise on short snippets where you know a bug is there; Bug Hunt gives you eight a round, with the cause and the fix after each.

Frequently asked questions

What is the most common bug in programming?
Off-by-one errors are a strong candidate: loops, ranges and slices that include one item too many or too few. They are easy to write and easy to miss in review.
How do I find a bug that gives no error?
Reproduce it with the smallest input you can, then check each step against what you expect, by printing values or with a debugger. Silent bugs usually come from a wrong comparison, a shared value or a boundary.
Why is NULL never equal to NULL in SQL?
NULL means an unknown value, so comparing it with anything, even another NULL, gives unknown rather than true. Use IS NULL and IS NOT NULL to test for it.
Found this useful? Share it:Post on X (opens in a new tab)

Games in this guide

Bug Hunt

Spot the buggy line in short JavaScript, Python and SQL snippets. 8 a round, with the cause and the fix after each.

  • Keyboard
  • Touch

Regex Runner

Write a regular expression that matches every string on one list and none on the other. 24 puzzles, from anchors to lookarounds.

  • Keyboard
  • Touch

Code Typer

A typing test for programmers: type real JavaScript, Python, Rust and SQL code against the clock.

  • Keyboard
  • Touch

All guides