json.dumps() indent= TypeError: can't multiply sequence by non-int (tested)

json.dumps(d, indent=2.5) fails with TypeError: can't multiply sequence by non-int of type 'float' — an error that never mentions indent, JSON, or even json.dumps() by name. We measured what indent actually accepts, what it silently tolerates, and why the error message looks nothing like the cause.

1. The normal case: a small positive int

import json

d = {"a": 1, "b": [1, 2, 3]}

json.dumps(d, indent=2)
'{\n  "a": 1,\n  "b": [\n    1,\n    2,\n    3\n  ]\n}'

Measured: an int indent is used directly as a number of spaces per nesting level, exactly as documented. Nothing surprising yet.

2. A float raises a TypeError that never mentions indent

json.dumps(d, indent=2.5)
TypeError: can't multiply sequence by non-int of type 'float'

Measured: the error comes from inside the encoder's internal implementation — it builds the indent whitespace by multiplying a string (" ") by your indent value, and Python's str * float is not allowed. The message says "sequence" and "non-int", not "indent" or "json.dumps()", so if this surfaces from deep inside a logging wrapper or serializer, it gives almost no hint about which argument or which call is actually wrong. We got the identical message passing a list for indent too — any non-int, non-str, non-None value that doesn't support str multiplication hits this same generic error.

3. A negative int is accepted and silently clamped to zero

json.dumps(d, indent=-2)
'{\n"a": 1,\n"b": [\n1,\n2,\n3\n]\n}'

json.dumps(d, indent=0)
'{\n"a": 1,\n"b": [\n1,\n2,\n3\n]\n}'

Measured: these two outputs are byte-for-byte identical. There is no error, no warning, and no indentation of any kind — just newlines with zero leading spaces. A negative value that reached indent through, say, a miscalculated "levels remaining" counter will not fail loudly; it will just quietly produce flat, newline-separated (but unindented) JSON that looks like a formatting bug, not a crash.

4. bool is accepted too, because bool is a subclass of int

json.dumps(d, indent=True) == json.dumps(d, indent=1)
True

json.dumps(d, indent=False) == json.dumps(d, indent=0)
True

Measured: True behaves exactly like 1 (one space of indent per level) and False behaves exactly like 0 (no indent, but still newlines) — with zero error either way. This is the same "bool is secretly an int" behavior that causes dict keys to be silently coerced elsewhere in json. In practice it means an accidentally-passed boolean config flag (e.g. a leftover pretty=True variable renamed to indent by mistake) won't be caught by any type check — it just produces unexpectedly minimal indentation instead of an error.

5. A string indent works differently, and is also unchecked

json.dumps(d, indent="\t")
'{\n\t"a": 1,\n\t"b": [\n\t\t1,\n\t\t2,\n\t\t3\n\t]\n}'

json.dumps(d, indent="")
'{\n"a": 1\n}'

Measured: a str is repeated per nesting level instead of a space, which is documented and useful for tabs — but an empty string behaves identically to indent=0: newlines with no visible indent, no error. There's no validation that the string is actually whitespace; indent="xx" works too and just repeats xx as the indent unit, producing output that looks broken rather than configurable.

6. What to actually check before passing indent=

If indent is computed rather than a literal, validate it's an int (and non-negative, if that matters to you) or a str before calling json.dumps() — the function itself will not catch a wrong type until it tries to multiply a string by it, and by then the error message points at the wrong place. A quick guard: assert indent is None or isinstance(indent, (int, str)), and separately reject isinstance(indent, bool) if a stray boolean must be treated as a bug rather than silently accepted as 0 or 1.

Frequently asked questions

Why does json.dumps(indent=) raise \"can't multiply sequence by non-int\"?

We measured that json's encoder builds indentation internally by multiplying a string by your indent value; passing a float, list, or other type that isn't int or str hits Python's own str-multiplication type check, producing this generic error with no mention of indent or json.dumps().

What does json.dumps(indent=-1) actually do?

We confirmed negative indent values are silently clamped to the same behavior as indent=0: newlines between items with zero leading whitespace, and no error or warning of any kind.

Does json.dumps(indent=True) work?

Yes — we measured that it's silently accepted and produces the exact same output as indent=1 (one space per level), because Python's bool is a subclass of int. indent=False likewise behaves exactly like indent=0.

Can indent= be a string instead of a number?

Yes, and we confirmed it's a documented feature: the string is repeated once per nesting level instead of spaces (useful for \"\\t\"), but an empty string produces the same flat output as indent=0 with no error either.

How do I validate indent= before it reaches json.dumps()?

We'd check isinstance(indent, (int, str)) or indent is None before the call, and separately reject isinstance(indent, bool) if a boolean reaching this argument should be treated as a bug rather than silently accepted as 0 or 1.

Written with AI assistance. Measured on 2026-10-03 with Python 3.13.5's built-in json module, calling json.dumps() with various indent= values (negative ints, floats, bool, string, empty string, list) and recording the exact output or exception for each. PlainHeaders is our extension; Python's json module is built in.