None & NoneType

Reviewed & published by Brayan K

None is Python's special constant that represents the deliberate absence of a value — the single instance of a type called NoneType that means "nothing here".

Part of the free Python course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.

You'll meet None everywhere: as the default return of a function, as a "not found" signal, and as the safe sentinel that fixes one of Python's most infamous beginner traps.

What You'll Learn in This Lesson

1 None — The Absence of a Value

None is not zero, not an empty string, and not False — it's a distinct object meaning "no value at all". It's the only value of type NoneType:

result = None
print(result)            # None
print(type(result))      # <class 'NoneType'>

# None is falsy, but it is NOT 0 or "" or False
print(None == 0)         # False
print(None == "")        # False
print(None == False)     # False
print(bool(None))        # False  (it IS falsy though)

# Use it as a deliberate placeholder
winner = None            # no winner decided yet
print("Decided?" , winner is not None)   # Decided? False

Reach for None whenever you need to say "this slot is intentionally empty" — a result not computed yet, a search that found nothing, or an optional setting left unset.

🔎 Test with "is None", not "== None"

Because there is exactly one None in the whole program, the right way to check for it is identity (is), not equality (==):

value = None

# Correct: identity check
print(value is None)        # True
print(value is not None)    # False

# A find that returns None when nothing matches
def find_user(users, name):
    for u in users:
        if u == name:
            return u
    return None    # explicit "not found"

found = find_user(["Ada", "Ben"], "Cleo")
if found is None:
    print("No such user")   # this runs
# None as a 'not found' signal, checked with 'is'
inventory = {"apple": 5, "pear": 2}

def stock(name):
    return inventory.get(name)   # .get returns None if missing

apple = stock("apple")
mango = stock("mango")

print("apple:", apple)                 # apple: 5
print("mango:", mango)                 # mango: None
print("mango missing?", mango is None) # mango missing? True

# Use it to provide a friendly message
print(apple if apple is not None else "out of stock")   # 5
print(mango if mango is not None else "out of stock")   # out of stock

# ✅ Expected output:
# apple: 5
# mango: None
# mango missing? True
# 5
# out of stock

2 Functions Return None by Default

If a function has no return (or a bare return), it hands back None. Methods that change an object in place do this too — and that causes a famous bug:

def greet(name):
    print("Hi", name)        # prints, but doesn't return

result = greet("Ada")        # Hi Ada
print(result)                # None  -> no return value

# The classic trap: sort() mutates and returns None
nums = [3, 1, 2]
nums = nums.sort()           # BUG: nums is now None!
print(nums)                  # None

# The fix: let sort() mutate, don't reassign
nums = [3, 1, 2]
nums.sort()                  # mutates in place
print(nums)                  # [1, 2, 3]
# Functions without return give back None; in-place methods do too
def add_tax(price):
    total = price * 1.1
    # oops - forgot to return!

print(add_tax(100))          # None

# Fixed version
def add_tax_fixed(price):
    return round(price * 1.1, 2)

print(add_tax_fixed(100))    # 110.0

# .append returns None, not the new list
items = [1, 2]
result = items.append(3)
print(result)                # None
print(items)                 # [1, 2, 3]

# ✅ Expected output:
# None
# 110.0
# None
# [1, 2, 3]

🧩 None as a Sentinel & the Mutable-Default Trap

⚠️ The bug: a mutable default is shared

Default values are created once, at definition time — so a default list is reused across every call and leaks data between them:

def add_item(item, basket=[]):   # DANGER: shared default list
    basket.append(item)
    return basket

print(add_item("a"))   # ['a']
print(add_item("b"))   # ['a', 'b']  <- 'a' leaked in!
print(add_item("c"))   # ['a', 'b', 'c']  <- still growing!

✅ The fix: default to None, build fresh inside

def add_item(item, basket=None):
    if basket is None:
        basket = []          # a brand-new list every call
    basket.append(item)
    return basket

print(add_item("a"))   # ['a']
print(add_item("b"))   # ['b']  <- clean every time
print(add_item("c"))   # ['c']

The None sentinel pattern — default to None, then replace it inside the function — is the standard cure for mutable defaults like [] and {}.

🎯 Your Turn: Fill in the Blanks

Replace each ___ to fix the mutable default and the None checks so the output matches.

# 🎯 YOUR TURN: replace each ___ to match the expected output
def log(message, history=___):     # 1. safe default sentinel
    if history ___ None:           # 2. correct None check
        history = []
    history.append(message)
    return history

print(log("start"))   # ['start']
print(log("stop"))    # ['stop']   (NOT ['start', 'stop'])

# 3. fill the blank so this prints 'empty'
result = None
print("empty" if result ___ None else "has value")

# Expected output:
# ['start']
# ['stop']
# empty
def log(message, history=None):    # 1. None
    if history is None:            # 2. is
        history = []
    history.append(message)
    return history

print("empty" if result is None else "has value")   # 3. is

Common Errors & How to Fix Them

❌ Reassigning the result of an in-place method

names = ["c", "a", "b"]
names = names.sort()   # names is now None!

✅ Either call names.sort() on its own, or use names = sorted(names) to get a new sorted list.

❌ Calling a method on a None value

user = None
user.upper()   # AttributeError: 'NoneType' has no attribute 'upper'

✅ Guard first: if user is not None: user.upper(). A "NoneType has no attribute" error usually means an earlier step returned None.

❌ Using a mutable default argument

def f(data={}):   # shared across all calls!
    ...

✅ Default to None and build the dict inside: if data is None: data = .

🎯 Mini Challenge: Safe Cache Builder

Write a function that caches results, using the None sentinel so each fresh call starts clean.

# Mini Challenge: per-call cache using the None sentinel
def remember(value, store=None):
    if store is None:
        store = {}
    store[value] = len(value)
    return store

print(remember("hi"))      # {'hi': 2}
print(remember("hey"))     # {'hey': 3}  -> NOT carrying 'hi' over

# Lookups return None when a key is absent
prices = {"tea": 2}
print(prices.get("tea"))   # 2
print(prices.get("milk"))  # None
print(prices.get("milk") is None)   # True

# Expected output:
# {'hi': 2}
# {'hey': 3}
# 2
# None
# True

📚 Keep Learning

Go deeper with the official Python documentation:

Frequently Asked Questions

Lesson complete — None holds no surprises now!

You know None is the absence of a value, you test it with is None, you understand the default-return and in-place-method traps, and you can wield the None sentinel to defeat mutable default arguments.

Practice quiz

What does None represent in Python?

  • The number zero
  • The deliberate absence of a value
  • An empty string
  • A boolean False

Answer: The deliberate absence of a value. None is a distinct object meaning 'no value at all' — it is not 0, '', or False. It is the sole instance of NoneType.

What is the type of None?

  • bool
  • int
  • NoneType
  • object

Answer: NoneType. type(None) is <class 'NoneType'>; None is the single value of that type.

Why is 'value is None' preferred over 'value == None'?

  • It is shorter to type
  • None is a unique singleton, so identity (is) is correct, fastest, and immune to overridden __eq__
  • == does not work on None
  • PEP 8 forbids 'is'

Answer: None is a unique singleton, so identity (is) is correct, fastest, and immune to overridden __eq__. Because there is only one None object, identity (is) is the correct and safe check; == could be fooled by a class overriding __eq__. PEP 8 recommends 'is None'.

What does a function with no return statement return?

  • 0
  • An empty string
  • None
  • It raises an error

Answer: None. A function with no return (or a bare return) automatically returns None.

What does None == 0 evaluate to?

  • True
  • False
  • None
  • It raises an error

Answer: False. None is not equal to 0, '', or False — None == 0 is False, even though None is itself falsy.

What is wrong with writing 'nums = nums.sort()'?

  • sort() is too slow
  • sort() mutates in place and returns None, so nums becomes None
  • sort() does not exist on lists
  • It sorts in reverse

Answer: sort() mutates in place and returns None, so nums becomes None. list.sort() sorts in place and returns None, so reassigning sets nums to None. Either call nums.sort() alone, or use sorted(nums).

What does list.append() return?

  • The new list
  • The appended item
  • None
  • The list length

Answer: None. append() mutates the list in place and returns None — like other in-place methods such as sort() and reverse().

Why is 'def f(items=[])' a dangerous default argument?

  • Lists cannot be defaults
  • The same list is created once and reused across all calls, leaking data between them
  • It is slower than a tuple
  • items is undefined

Answer: The same list is created once and reused across all calls, leaking data between them. Default values are created once at definition time, so a default list is shared across every call and accumulates data between them.

What is the standard fix for a mutable default argument?

  • Use an empty tuple
  • Default to None, then create the object inside: if items is None: items = []
  • Use a global list
  • Make the argument required

Answer: Default to None, then create the object inside: if items is None: items = []. The None sentinel pattern — default to None and build a fresh list/dict inside the function — gives each call its own object.

Calling a method on a None value, like user.upper() when user is None, raises what?

  • TypeError
  • ValueError
  • AttributeError: 'NoneType' object has no attribute 'upper'
  • Nothing — it returns None

Answer: AttributeError: 'NoneType' object has no attribute 'upper'. None has no such method, so you get AttributeError. This usually means an earlier step unexpectedly returned None.

Continue this course

Frequently asked questions

What is None in Python?

None is a special constant that represents the absence of a value — 'nothing here'. It's the sole instance of a type called NoneType, and it's what a function returns when you don't write a return statement. Use it as a deliberate placeholder for 'no result yet', 'not found', or 'optional argument was omitted'.

Why use 'is None' instead of '== None'?

Because None is a unique singleton — there is only ever one None object in a program — identity (is) is the correct, fastest, and safest check. With '==' a custom class could override __eq__ and lie about being equal to None, producing surprising results. The official style guide (PEP 8) explicitly says to use 'is None' / 'is not None'.

What does a function return if it has no return statement?

It returns None automatically. The same happens with a bare 'return' that has no value, or after a return that's never reached. That's why list.append() or print() seem to 'return nothing' — they actually return None. A classic bug is writing 'nums = nums.sort()', which sets nums to None because sort() mutates in place and returns None.

Why shouldn't I use a mutable object as a default argument?

Default argument values are created once, when the function is defined, not each time it's called. So a default like def f(items=[]) reuses the SAME list across every call, and changes leak between calls. The fix is the None sentinel: default to None, then inside the function do 'if items is None: items = []' to make a fresh list every call.