Skip to Content
None

None

None is Python’s null value. It is the only instance of NoneType and represents the absence of a value, an empty result, or an uninitialized state.

None shows up in almost every Python program, from return values to default arguments. Understanding exactly when Python produces it and how to check for it correctly saves a lot of subtle bugs.

What is None

None is a built-in constant. There is only ever one None object in the entire interpreter; it is a singleton. Every name that holds None points to the same object.

type(None) # <class 'NoneType'> id(None) # same id every time

Truthiness: None is falsy. bool(None) is False. Use if x is None: to check for None specifically, not if not x: (which matches any falsy value).

bool(None) # False not None # True

Representation: str(None) returns the string "None". repr(None) also returns "None". In an f-string, None renders as the text "None".

str(None) # "None" repr(None) # "None" f"result: {None}" # "result: None"

Checking for None

Always use is or is not to test for None. Never use == or !=.

CheckCorrect?Why
x is NoneYesIdentity check; always correct since None is a singleton
x is not NoneYesIdentity check; correct way to confirm a value is present
x == NoneNoEquality check; custom __eq__ on objects can return True
x != NoneNoSame issue; relies on __eq__; misleading
x = None x is None # True x is not None # False # Example showing why == is unreliable class Tricky: def __eq__(self, other): return True obj = Tricky() obj == None # True - wrong; obj is not None obj is None # False - correct

Why is works: Since None is a singleton, every None is literally the same object. The is operator checks identity (same object in memory), which is always correct for None. The == operator checks equality using __eq__, which can be overridden.

None as a Return Value

Functions that reach the end without a return statement implicitly return None. A return with no value also returns None.

def greet(name): print(f"Hello, {name}") result = greet("Alice") print(result) # None - function printed but returned nothing def explicit_none(): return # same as return None explicit_none() is None # True

In-place methods return None: Many built-in methods that mutate an object in place return None to signal that the operation happened in place and no new object was created.

MethodReturnsCommon mistake
list.sort()Nonelst = lst.sort() sets lst to None
list.append()Nonelst = lst.append(x) sets lst to None
list.extend()NoneSame issue
list.reverse()NoneSame issue
dict.update()NoneSame issue
set.add()NoneSame issue
nums = [6, 2, 8, 4] nums = nums.sort() # BUG: nums is now None; sort() returns None print(nums) # None # Correct nums = [6, 2, 8, 4] nums.sort() # mutates in place; do not assign print(nums) # [2, 4, 6, 8]

None vs Other Falsy Values

None is falsy, but several other values are also falsy. They are not the same thing.

ValueFalsy?TypeMeaning
NoneYesNoneTypeAbsent or uninitialized value
0YesintThe number zero
0.0YesfloatThe float zero
""YesstrEmpty string
[]YeslistEmpty list
{}YesdictEmpty dict
()YestupleEmpty tuple
set()YessetEmpty set
FalseYesboolBoolean false

Using if not x: when you mean “if x is None” is a bug waiting to happen: it catches empty lists, zero, and empty strings too.

def process(data): if not data: # BUG: also triggers on [], 0, "" print("no data") def process_correct(data): if data is None: # only triggers on None print("no data provided")

None as a Default Parameter

None is the standard way to give a parameter an optional default, especially when that default would otherwise be a mutable object like a list or dictionary.

The mutable default trap: If a mutable object is used as a default argument, it is created once when the function is defined and shared across all calls that omit the argument. This leads to state leaking between calls.

# Bad - the same list is reused across calls def add_item(value, container=[]): container.append(value) return container add_item(2) # [2] add_item(4) # [2, 4] - unexpected; same list add_item(6) # [2, 4, 6] - keeps growing

The fix: Use None as the default and create a fresh object inside the function body.

# Correct - each call gets a new list def add_item(value, container=None): if container is None: container = [] container.append(value) return container add_item(2) # [2] add_item(4) # [4] - fresh list each time add_item(6) # [6] # Pass an existing list to reuse it shared = [2, 4] add_item(6, shared) # [2, 4, 6]; shared is mutated

None as a Sentinel Value

A sentinel is a special value used to signal “nothing here” or “not found.” None is the most common sentinel in Python.

def find_even(nums): """Return the first even number, or None if there are none.""" for n in nums: if n % 2 == 0: return n return None result = find_even([1, 3, 5]) if result is None: print("not found") else: print(result)

dict.get() uses None: When a key is missing, dict.get(key) returns None by default. Pass a second argument to use a different sentinel.

data = {"a": 2, "b": 4} data.get("c") # None - missing key data.get("c", 0) # 0 - custom default

When None is a valid value: If None itself can be a legitimate value in a dictionary, using get() becomes ambiguous. In that case, use a custom sentinel object.

MISSING = object() # unique sentinel; not None data = {"a": None, "b": 4} data.get("a", MISSING) is MISSING # False - key exists, value is None data.get("c", MISSING) is MISSING # True - key is truly absent

None in Type Hints

In type annotations, None appears as the return type for functions that return nothing, or combined with another type to signal “this might be None.”

def log(message: str) -> None: print(message)

For values that can be None, use Optional[T] (from typing) or the shorter T | None syntax (Python 3.10+). Both mean the same thing.

from typing import Optional def find(nums: list[int], target: int) -> Optional[int]: for i, n in enumerate(nums): if n == target: return i return None # Python 3.10+ shorthand def find_modern(nums: list[int], target: int) -> int | None: for i, n in enumerate(nums): if n == target: return i return None

Optional[T] is exactly Union[T, None]: There is no difference; Optional is just a convenience alias.

from typing import Optional, Union # These two are identical def f(x: Optional[int]) -> None: ... def f(x: Union[int, None]) -> None: ...

None in Comparisons

None can be compared for equality (==) and identity (is), but not for ordering. Comparing None with <, >, <=, or >= raises a TypeError in Python 3.

None == None # True (but use is) None is None # True (correct) None == 0 # False None == False # False None == "" # False None < 0 # TypeError: '<' not supported between instances of 'NoneType' and 'int' None > 0 # TypeError

Sorting with None: If a list contains None, calling sort() on it raises TypeError because None cannot be compared with numbers or strings. Use a key function to handle it.

nums = [4, None, 2, None, 8] nums.sort() # TypeError # Push None values to the end nums.sort(key=lambda x: (x is None, x if x is not None else 0)) # [2, 4, 8, None, None]

filter(None, iterable)

Passing None as the first argument to filter() is a quick way to remove all falsy values from an iterable. It keeps only elements where bool(element) is True.

values = [0, 2, None, 4, "", 6, False, 8] list(filter(None, values)) # [2, 4, 6, 8]

Note that this removes 0, "", False, and None all at once. If only None values should be removed, use a comprehension instead.

values = [0, 2, None, 4, 6] no_none = [x for x in values if x is not None] # [0, 2, 4, 6] - 0 kept

Common Patterns with None

Guarding before use

Check for None before accessing attributes or calling methods on a value that might be None.

def get_length(text): if text is None: return 0 return len(text)

Short-circuit with or

Use or to substitute a default when a value might be None. This works but has the same pitfall as if not x: in that it also replaces falsy values like 0 or "".

name = None display = name or "Unknown" # "Unknown" count = 0 result = count or 2 # 2 - but 0 is a valid count; this is a bug

Use if x is None when 0 or "" are valid values.

count = 0 result = count if count is not None else 2 # 0 - correct

Chaining method calls safely

When a function may return None, calling a method on its result crashes with AttributeError. Guard before chaining.

def lookup(key): data = {"a": [2, 4, 6]} return data.get(key) result = lookup("a") if result is not None: print(result[0]) # 2 result = lookup("b") # result is None; result[0] would raise TypeError

None vs NoneType

None is the value. NoneType is the type. To check if something is None, use is None. Using isinstance(x, type(None)) works but is verbose and unusual.

x = None x is None # True - idiomatic isinstance(x, type(None)) # True - works but unusual type(x) is type(None) # True - also works but unusual

Tricky Behaviors

Checking - == vs is

x == None can give wrong results if x has a custom __eq__. Always use x is None to check for None.

None is falsy, but not uniquely so

if not x: catches None, 0, "", [], {}, and False. When only None should be handled, use if x is None:.

In-place methods return None

list.sort(), list.append(), list.reverse(), dict.update(), and set.add() all return None. Assigning their result (e.g. lst = lst.sort()) sets the variable to None and discards the object.

Functions without return return None

Every function in Python returns a value. If no return is reached, the return value is None. A bare return also returns None.

Mutable default argument

Using def f(x=[]) reuses the same list for every call. Use def f(x=None) and if x is None: x = [] inside the body.

None in f-strings

f"{None}" produces the string "None", not an empty string or error. This can cause unintended output if None slips into a template.

value = None print(f"score: {value}") # "score: None" - may not be intended

None and ordering

None cannot be compared with < or >. Sorting a list that contains None raises TypeError. Handle None explicitly with a key function.

None vs missing attribute

getattr(obj, "x", None) returns None if attribute x does not exist. But if x exists and its value is None, it also returns None. Use hasattr(obj, "x") first when you need to distinguish between “missing” and “set to None.”

dict.get() ambiguity

d.get("key") returns None for both a missing key and a key whose value is None. If None is a valid stored value, use a custom sentinel: d.get("key", MISSING) and check is MISSING.

filter(None, …) removes all falsy values

filter(None, iterable) removes 0, "", False, [], and None. To remove only None, use [x for x in it if x is not None].

Optional[T] is Union[T, None]

Optional[int] and Union[int, None] and int | None (Python 3.10+) are all identical. None of them enforce anything at runtime; they are only hints for type checkers.

None is a singleton

There is exactly one None object. All variables holding None point to the same object. This is why is works for checking it.

Comparing None with None

None == None is True, but None < None raises TypeError. None supports equality but not ordering.

Interview Questions

What is None and what is its type?

None is Python’s null value, representing the absence of a value. Its type is NoneType. It is a singleton; there is exactly one None object in the interpreter.

Why use is None instead of == None?

is checks object identity. Since None is a singleton, x is None is always correct. x == None uses __eq__, which can be overridden by custom classes to return True even when the object is not None. is None is the idiomatic and safe way to check.

What does a Python function return if there is no return statement?

It returns None implicitly. A bare return with no value also returns None.

Why do in-place methods like list.sort() return None?

They return None to make it clear that the operation happened in place and no new object was created. This prevents the mistake of chaining a mutation with the assumption that a new object is returned. Use sorted(lst) when a new sorted list is needed.

What is the mutable default argument trap and how does None fix it?

When a mutable object like [] or {} is used as a default argument, Python creates it once when the function is defined and reuses it across every call. Mutations in one call persist into the next. Using None as the default and creating a new object inside the body with if x is None: x = [] gives each call a fresh object.

What is the difference between None and other falsy values?

None means “no value.” 0 is the integer zero. "" is an empty string. [] is an empty list. They are all falsy, but they have different types and meanings. Use x is None to check specifically for None and not accidentally catch 0 or "".

How do you handle a dict key that might be absent vs a key whose value is None?

dict.get(key) returns None for both cases. To distinguish them, use a custom sentinel: define MISSING = object() and call d.get(key, MISSING). If the result is MISSING, the key is absent. If the result is None, the key exists with a None value.

Can None be compared with < or >?

No. Python 3 raises TypeError for ordering comparisons involving None. Sorting a list that contains None raises TypeError. Handle it with a key function that moves None values to the beginning or end.

What is Optional[T] in type hints?

Optional[T] is shorthand for Union[T, None], meaning the value can be either of type T or None. In Python 3.10+, the same is written as T | None. These are only hints for type checkers; they have no effect at runtime.

What does filter(None, iterable) do?

It removes all falsy values from the iterable, keeping only elements where bool(element) is True. This removes None, 0, "", [], False, and any other falsy value. To remove only None while keeping 0 or "", use [x for x in iterable if x is not None].

How do you distinguish between a missing attribute and an attribute set to None?

Use hasattr(obj, "attr") to check existence first. getattr(obj, "attr", None) returns None for both a missing attribute and one set to None, so it cannot distinguish between the two cases on its own.

Why does embedding None in an f-string produce the text “None” instead of an error?

Python calls str() on values inside f-strings. str(None) returns the string "None". There is no exception; the text “None” appears in the output, which may be unintended if the variable was supposed to hold a real value.

Is None a valid dictionary value? How do you detect it?

Yes. None is a valid value for any dictionary key. To check whether a key maps to None versus being absent, avoid dict.get() and instead use key in d to confirm the key exists, then read d[key] directly.

What is the difference between None and False in a boolean context?

Both are falsy, so if None: and if False: both skip the body. But they are different types and different objects. None is False is False. Use is None when you specifically mean “no value was provided.”

Last updated on