donnemartin/interactive-coding-challenges · error · TypeError

coins or total cannot be None

Error message

coins or total cannot be None

What it means

CoinChanger.make_change (minimum coins to make a total, memoized via a cache dict) raises TypeError when coins or total is None. The guard precedes the trivial cases (empty coins or total == 0 return 0) and the recursive _make_change helper. It exists because iterating None coins or comparing None totals would produce obscure failures inside the DP.

Source

Thrown at recursion_dynamic/coin_change_min/coin_change_min_solution.ipynb:129

   "metadata": {},
   "source": [
    "## Code"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "import sys\n",
    "\n",
    "\n",
    "class CoinChanger(object):\n",
    "\n",
    "    def make_change(self, coins, total):\n",
    "        if coins is None or total is None:\n",
    "            raise TypeError('coins or total cannot be None')\n",
    "        if not coins or total == 0:\n",
    "            return 0\n",
    "        cache = {}\n",
    "        return self._make_change(coins, total, cache)\n",
    "\n",
    "    def _make_change(self, coins, total, cache):\n",
    "        if total == 0:\n",
    "            return 0\n",
    "        if total in cache:\n",
    "            return cache[total]\n",
    "        min_ways = sys.maxsize\n",
    "        for coin in coins:\n",
    "            if total - coin < 0:\n",
    "                continue\n",
    "            ways = self._make_change(coins, total - coin, cache)\n",
    "            if ways < min_ways:\n",
    "                min_ways = ways\n",
    "        cache[total] = min_ways + 1\n",

View on GitHub (pinned to 358f2cc604)

Solutions

  1. Pass a concrete list of positive coin denominations and an int total
  2. Treat missing config as empty list: coins = coins or [] — but decide whether that 'no coins' semantic (return 0) is what you want
  3. Validate and convert inputs (int(total), list of ints) before calling make_change

Example fix

// before
changer.make_change(coins_from_config, total)  # coins_from_config is None
// after
changer.make_change(coins_from_config or [], total)
Defensive patterns

Strategy: validation

Validate before calling

coins = coins or []
if total is None:
    total = 0
changer.make_change(coins, total)

Type guard

def is_coin_input(coins, total):
    return isinstance(coins, list) and all(isinstance(c, int) and c > 0 for c in coins) and isinstance(total, int)

Try / catch

try:
    changer.make_change(coins, total)
except TypeError as e:
    if 'coins or total cannot be None' in str(e):
        raise ValueError('coins list and total are required') from e
    raise

Prevention

When it happens

Trigger: Calling CoinChanger().make_change(None, 11) or make_change([1,2,5], None); passing a coin list loaded from config where the denominations key was omitted, or a total from a failed int() parse.

Common situations: Denominations read from JSON/YAML where the field is optional; totals derived from amounts that can be None for 'not provided'; callers assuming empty-input cases ([]) are handled the same as None — they are not: [] returns 0 silently while None raises.

Related errors


AI-assisted analysis of donnemartin/interactive-coding-challenges@358f2cc604 (2026-08-28). Data as JSON: /api/errors/43458454ca2051e3. Report an issue: GitHub.