<!--
title: Consistent None Handling
domain: ai-agents
topic: Null Handling
language: Python
source: langchain-ai/langchain
updated: 2026-02-24
url: https://awesomereviewers.com/reviewers/langchain-consistent-none-handling/
-->

When a value can be missing/null, reflect it in the type system and handle it explicitly at the boundary—never leak `None` into observable outputs (yields, dict keys, metadata, merged content) and don’t “paper over” missing invariants with synthetic placeholders.

Practical rules:
1) Prefer `X | None = None` (or `Optional[X]`) over sentinel-literals like `Literal[False]` for optional callables.
2) Guard before writing/using: if something derived could be `None` (e.g., a dict key), check before assignment.
3) Ensure iterators/streaming never yield `None` as a valid element unless the type says so.
4) Keep semantic distinctions: e.g., treat `args=None` as “streaming in-progress” and `args={}` as “completed no-args” to avoid parsing/execution bugs.
5) If an invariant is violated (e.g., “an AIMessage must exist”), either raise a clear error or widen the function signature and make callers handle the missing case—don’t create dummy objects that hide the underlying state bug.

Example patterns:
```py
from typing import Callable

# 1) Optional callable
length_fn: Callable[[list[str]], list[int]] | None = None

# 2) Guard None-derived keys
header_key = splittable_headers.get("#" * depth)
if header_key is not None:
    current_chunk.metadata[header_key] = value

# 3) Avoid yielding None
first_chunk = await first_map_chunk_task
if first_chunk is not None:
    yield first_chunk

# 4) Preserve streaming vs final semantics
# args=None => not ready to execute; args={} => execute with no args
if tool_call_args is None:
    # streaming/in-progress
    pass
else:
    # final state
    run_tool_with(tool_call_args)

# 5) Missing invariant: raise or widen types (don’t fabricate)
if last_ai_message is None:
    raise ValueError("Expected at least one AIMessage in state")
```
