Skip to content

Truthiness & Type Conversion Interview Questions & Answers

16 questions Updated 2026-06-18 Share:

Python interview questions on falsy values, __bool__/__len__, explicit type conversion, truthiness of empty containers, short-circuiting and/or, and is None vs == None.

Read the in-depth guidePython Truthiness and Type Conversion Explained — Falsy Values, and/or, and __bool__(opens in new tab)
16 of 16

A handful of values are falsy (treated as False in a boolean context): None, False, zero of any numeric type (0, 0.0, 0j), and empty containers/sequences ("", [], {}, (), set(), range(0)). Almost everything else is truthy.

bool(0), bool(""), bool([]), bool(None)   # all False
bool(1), bool("x"), bool([0]), bool(" ")  # all True
if not items:        # idiomatic empty check
    print("empty")

Why it matters: idiomatic Python uses if items: rather than if len(items) > 0:. Rule of thumb: "empty or zero or None" is falsy; everything else is truthy.

Python calls __bool__ first; if it's not defined, it falls back to __len__ (zero length is falsy). If neither exists, the object is always truthy — the default for plain instances.

class Box:
    def __init__(self, items): self.items = items
    def __len__(self): return len(self.items)   # used for truthiness

bool(Box([]))     # False — __len__ is 0
bool(Box([1]))    # True

class Always:
    def __bool__(self): return False   # __bool__ wins over __len__
bool(Always())    # False

Rule of thumb: define __bool__ (or __len__) so if obj: makes sense for your type; otherwise every instance is truthy.

Python provides constructor functions that convert between types: int(), float(), str(), bool(), list(), tuple(), set(), dict(). They build a new object and raise ValueError if the input can't be converted.

int("42")        # 42
int("3.9")       # ValueError — not a valid int literal
int(3.9)         # 3   — truncates toward zero
float("3.14")    # 3.14
str(255)         # "255"
list("abc")      # ['a', 'b', 'c']
list({1: "a"})   # [1]  — iterates keys

Rule of thumb: these are explicit conversions you call yourself — Python rarely converts types implicitly, so reach for the constructor you need.

Yes. Every empty built-in container is falsy — [], {}, (), set(), "" — and so is None. A non-empty container is truthy regardless of what it contains, even [0] or [False].

bool([]), bool({}), bool(set()), bool("")   # all False
bool([0]), bool([False]), bool({None})       # all True — non-empty!
bool(None)                                   # False

Watch the trap: [0] is truthy because it has one element, even though that element is falsy. Rule of thumb: container truthiness depends on length, not on the elements' values.

and/or short-circuit and return one of their operands, not a strict True/False. and returns the first falsy operand (or the last if all are truthy); or returns the first truthy operand (or the last if all are falsy).

0 and 5         # 0   — first falsy, second never evaluated
2 and 5         # 5   — both truthy, returns last
0 or "default"  # "default"  — first truthy
None or 0 or [] # []  — all falsy, returns the last
name = user_input or "guest"   # common default idiom

Rule of thumb: x or default supplies a fallback, and short-circuiting means the right side is skipped when the result is already decided.

None is a singleton — there is exactly one None object — so is None checks identity, which is fast and can't be fooled. == None calls __eq__, which a class can override to return a misleading result.

if x is None:        # idiomatic, reliable
    ...

class Weird:
    def __eq__(self, other): return True
Weird() == None      # True   — misleading!
Weird() is None      # False  — correct

Rule of thumb: always compare against None, True, and False with is/ is not — PEP 8 explicitly recommends it.

if x: is falsy for many valid values — 0, "", [], 0.0 — not just None. If your real question is "was an argument supplied?", a bare truthiness check silently rejects legitimate empty/zero inputs.

def f(count=None):
    if not count:          # BUG: triggers for count=0 too
        count = 10
    return count
f(0)                       # 10  — wanted 0!

def g(count=None):
    if count is None:      # correct: only the unset case
        count = 10
    return count
g(0)                       # 0

Rule of thumb: distinguish "missing" from "empty/zero" — use is None for sentinels, reserve if x: for genuine emptiness checks.

bool(n) is False only for zero; any non-zero number is True. bool(s) is False only for the empty string — so bool("0") and bool("False") are both True (non-empty strings are truthy regardless of content).

bool(0), bool(0.0), bool(-1)     # (False, False, True)
bool(""), bool("0"), bool("False")  # (False, True, True)  — trap!
int(True), int(False)            # (1, 0)

Rule of thumb: parsing input like "0"/"false" needs explicit checks — never trust bool(string) to interpret the content, only its emptiness.

any(iterable) returns True if at least one element is truthy; all returns True if every element is truthy. Both short-circuit and have edge cases on empty input: all([]) is True (vacuous truth), any([]) is False.

any([0, "", 3])      # True  — 3 is truthy
all([1, 2, ""])      # False — "" is falsy
all([])              # True  — nothing fails
any([])              # False — nothing succeeds
all(x > 0 for x in nums)   # combine with a generator

Rule of thumb: all/any over a generator expression is the idiomatic "do all/any items satisfy this?" — but remember all([]) is True.

Python implicitly widens numbers in mixed arithmetic: int → float → complex, picking the more general type. It does not implicitly convert between strings and numbers — that always raises TypeError.

1 + 2.0        # 3.0   — int promoted to float
3 + 4j         # (3+4j) — promoted to complex
True + 1.5     # 2.5   — bool is an int
"3" + 4        # TypeError — no str/int coercion

Rule of thumb: numeric types auto-promote to the wider type; everything else needs an explicit constructor (int(s), str(n)).

It compares values via chaining: a == b == c means a == b and b == c, with b evaluated once. It is not (a == b) == c, which would compare a bool to c.

1 == 1 == 1        # True  — (1==1) and (1==1)
(1 == 1) == 1      # True  — but for the wrong reason: True == 1
(1 == 1) == 2      # False — True == 2
True == 1 == 1.0   # True  — bool/int/float all equal here

Rule of thumb: chained == checks all neighbors are equal; never parenthesize it as (a == b) == c, which collapses a comparison into a bool.

int(s, base) parses a string in any base 2-36; bin(), oct(), hex() produce prefixed string representations. The reverse and forward directions are separate tools.

int("ff", 16)     # 255
int("1010", 2)    # 10
int("0o17", 0)    # 15  — base 0 auto-detects from prefix
bin(10)           # '0b1010'
hex(255)          # '0xff'
format(255, "x")  # 'ff'  — no prefix

Rule of thumb: int(s, base) to parse, bin/oct/hex to produce; use format or f-strings when you want the digits without the 0b/0x prefix.

Converting to set or dict drops duplicates and order info, and converting a dict to a list/tuple yields its keys, not items. These lossy conversions catch people off guard.

list({3, 1, 2})        # order not guaranteed by value
set([1, 1, 2])         # {1, 2} — dups removed
list({"a": 1, "b": 2}) # ['a', 'b'] — keys only
dict([("a", 1), ("b", 2)])  # {'a': 1, 'b': 2} — from pairs
tuple("ab")            # ('a', 'b')

Rule of thumb: list(dict) gives keys (use .items() for pairs), and set() is a quick dedupe but discards order and duplicate counts.

str() produces a readable form for end users (via __str__); repr() produces an unambiguous form for developers (via __repr__), ideally one you could paste back. If __str__ is missing, str() falls back to __repr__.

import datetime
d = datetime.date(2026, 6, 19)
str(d)        # '2026-06-19'  — friendly
repr(d)       # 'datetime.date(2026, 6, 19)'  — reconstructable
str("hi")     # 'hi'
repr("hi")    # "'hi'"  — shows the quotes

Rule of thumb: implement __repr__ for every class (debugging/logging); add __str__ only when users need a prettier form.

ord(c) returns the integer Unicode code point of a single character; chr(n) returns the character for a code point. They are inverses and work across the full Unicode range.

ord("A")        # 65
chr(65)         # "A"
ord("€")        # 8364
chr(8364)       # "€"
[chr(ord("a") + i) for i in range(3)]   # ['a', 'b', 'c']

Rule of thumb: ord/chr bridge characters and integers — useful for ciphers, alphabets, and ranges; they handle Unicode, not just ASCII.

nan is truthy — truthiness for floats depends only on being non-zero, and nan is not zero. This surprises people who expect "not a number" to behave like a falsy blank.

bool(float("nan"))   # True!
bool(0.0)            # False
import math
if math.isnan(x):    # the correct way to handle nan
    ...

Rule of thumb: never use truthiness to detect nan — it's truthy; test explicitly with math.isnan().

More ways to practice

The self-quiz is live. Join our channel for updates, new content & tech tips.

Join our WhatsApp Channel