# None-coalescing operator and null‑coalescing assignment

**URL:** <https://discuss.python.org/t/none-coalescing-operator-and-null-coalescing-assignment/107510>\
**Category:** Ideas\
**Tags:** pep\
**Created:** [May 24, 2026, 9:00am UTC](https://discuss.python.org/t/none-coalescing-operator-and-null-coalescing-assignment/107510 "2026-05-24T09:00:40Z")\
**Posts on this page:** 20\
**Page:** 1

<div class="post-metadata">

**Author:** ![Rainwalker](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/rainwalker/32/35760_2.png) [@Rainwalker](https://discuss.python.org/u/Rainwalker)\
**Post date:** [May 24, 2026, 9:00am UTC](https://discuss.python.org/t/none-coalescing-operator-and-null-coalescing-assignment/107510/1 "2026-05-24T09:00:40Z")

</div>

# [Pre-PEP] None-coalescing operator `??` and null-coalescing assignment `??=`

Author: Rainwalker

Target version: Python 3.16 (or later)

Status: Pre‑PEP (discussion)

* * *

**Note** : This is a narrow subset of PEP 505 (no `?.` or `?[]`).

* * *

# Problem

Currently, the standard way to provide a default value when something is `None` is:

```python
value = get_value() if get_value() is not None else default

```

This has two major issues:

1. Redundancy – the `expression get_value()` is written twice. When the expression is long or has side effects, this becomes error‑prone and hurts readability.
2. Double evaluation & side effects – if `get_value()` is a function call, it is executed twice. Worse, if it mutates state (e.g., `list.pop()`), the second evaluation may produce a different value or corrupt data.

A workaround is to store the result in a temporary variable:

```python
tmp = get_value()
value = tmp if tmp is not None else default

```

But this adds an extra line and still feels verbose.

The `or` operator is not suitable because it treats all falsy values (`0`, `False`, `""`, `[]`, etc.) as triggers, not only `None`:

```python
value = get_value() or default # wrong when get_value() returns 0 or False

```

Similarly, assigning a default value only when a variable is None requires an explicit check:

```python
if x is None:
    x = default

```

This is verbose and breaks the flow of expression‑oriented code.

* * *

# Proposal

Introduce two new operators:

1. None‑coalescing operator `??`

```python
value = get_value() ?? default

```

- Evaluates the left‑hand side.
- If the left‑hand side is not `None`, that value is returned and the right‑hand side is not evaluated (short‑circuiting).
- If the left‑hand side is `None`, the right‑hand side is evaluated and returned.

* * *

# Chainability

Multiple `??` operators can be chained. The expression returns the first non‑None value:

```python
result = a ?? b ?? c ?? d

```

This is equivalent to:

```python
if a is not None:
    result = a
elif b is not None:
    result = b
elif c is not None:
    result = c
else:
    result = d

```

* * *

# Precedence

The `??` operator should have the same precedence as `or`. For example:

```python
a + b ?? c * d # parsed as (a + b) ?? (c * d)

```

* * *

# None-coalescing assignment `??=`

```python
x ??= default

```

This is a shorthand for:

```python
if x is None:
    x = default

```

However, unlike other augmented assignments (`+=`, `|=`, etc.), `??=` evaluates the right‑hand side only if the left‑hand side is `None` (short‑circuiting). It does not return a value – it is a statement, just like `+=`.

* * *

# Examples

```python
# ?? – expression
first_title = web_search("python") ?? "No results found"
user = db.fetch(user_id) ?? default_user
data = api.get(key) ?? cache.get(key) ?? fallback_value

# Avoid double evaluation
value = expensive() ?? default # instead of expensive() if expensive() is not None else default

# Safe with side‑effect functions
data = stack.pop() ?? backup_data

# ??= – assignment
key ??= compute_default(key) # only compute if missing
config.timeout ??= 5.0
user.nickname ??= "Anonymous"

# Works with any assignment target (variable, attribute, subscript)
obj.attr ??= 42
lst[index] ??= 0

```

* * *

# Why this belongs in Python

- Other mainstream languages already have `??` – C#, JavaScript, PHP, Swift, Kotlin (Elvis operator). Many of them also have `??=` (e.g., C#, JavaScript, PHP). This demonstrates utility and low learning curve.
- Addresses real, frequent patterns – Many codebases contain `x if x is not None else y` or `if x is None: x = default`.
- Does not break backward compatibility – `??` and `??=` are currently syntax errors.
- `??=` is a natural extension – just like `+=` complements `+`, `??=` complements `??`.

* * *

# Open questions for discussion

1. Precedence of `??` – Should it have the same precedence as `or`? (Proposed: yes.)
2. Chaining of `??` – Should `a ?? b ?? c` be allowed? (Proposed: yes.)
3. Interaction with await – Should `await foo() ?? default` be allowed? (Proposed: yes.)
4. `??=` for attributes/subscripts – Should it work with `obj.attr ??= val and lst[i] ??= val`? (Proposed: yes – same as regular assignment.)

* * *

# Rejected alternatives

- `or` – does not distinguish None from other falsy values.
- if‑expression – verbose and repeats the expression.
- coalesce built‑in function – cannot provide short‑circuiting without a lambda, which harms readability.
- Separate PEP for `??=` – better to propose together as a consistent pair.

* * *

# Implementation sketch

The change would require:

- Adding new tokens `??` and `??=` to the grammar.
- Adding new AST nodes (or reusing `BinOp` with a new operator for `??`; a new assignment node for `??=`).
- Implementing short‑circuit evaluation for both.
- Updating the precedence table.

No changes to existing syntax or semantics are needed.

* * *

# Questions for the community

- Are `??` and `??=` desirable enough to warrant new operators?
- Should we adopt the exact same semantics as in C#/JavaScript (only None triggers fallback)?
- Are there any hidden pitfalls with short‑circuiting or precedence?

* * *

Thank you for reading! I look forward to your feedback.

---

<div class="post-metadata">

**Author:** ![ronaldoussoren](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/ronaldoussoren/32/61_2.png) [@ronaldoussoren](https://discuss.python.org/u/ronaldoussoren)\
**Post date:** [May 24, 2026, 9:23am UTC](https://discuss.python.org/t/none-coalescing-operator-and-null-coalescing-assignment/107510/2 "2026-05-24T09:23:54Z")

</div>

See also [PEP 505 – None-aware operators](https://peps.python.org/pep-0505/) and previous discussions about this.

---

<div class="post-metadata">

**Author:** ![HexDecimal](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/hexdecimal/32/28376_2.png) [@HexDecimal](https://discuss.python.org/u/HexDecimal)\
**Post date:** [May 24, 2026, 9:27am UTC](https://discuss.python.org/t/none-coalescing-operator-and-null-coalescing-assignment/107510/3 "2026-05-24T09:27:38Z")

</div>

Community guidelines are to check for existing posts before starting a new ones. Is there anything new here which hasn’t been discussed previously?

edit: the post now makes more sense as being a subset of PEP 505 excluding safe navigation features.

---

<div class="post-metadata">

**Author:** ![Rainwalker](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/rainwalker/32/35760_2.png) [@Rainwalker](https://discuss.python.org/u/Rainwalker)\
**Post date:** [May 24, 2026, 9:28am UTC](https://discuss.python.org/t/none-coalescing-operator-and-null-coalescing-assignment/107510/4 "2026-05-24T09:28:24Z")

</div>

I have studied PEP 505 and the historical discussions carefully. While the full PEP 505 (including `?.` and `?[]`) was deferred due to complexity and concerns about debugging, my proposal is a **deliberately narrow subset** – it only includes `??` and `??=`, excluding the controversial parts entirely.

I believe `??` deserves a fresh look for two reasons:

1. It solves the real problem that `or` cannot distinguish `0`/`False` from `None`.
2. More importantly, `??=` provides **atomicity** that the manual `if x is None: x = default` cannot offer – no race condition between the check and the assignment, which matters in concurrent contexts.

Given that this is the safest, most focused part of PEP 505, I think it’s worth reconsidering on its own merits.

Looking forward to your further thoughts.

---

<div class="post-metadata">

**Author:** ![Rainwalker](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/rainwalker/32/35760_2.png) [@Rainwalker](https://discuss.python.org/u/Rainwalker)\
**Post date:** [May 24, 2026, 9:31am UTC](https://discuss.python.org/t/none-coalescing-operator-and-null-coalescing-assignment/107510/5 "2026-05-24T09:31:50Z")

</div>

You’re right to call this out. I should have been more explicit about prior discussions in the opening post. My apologies.

That said, I do believe there’s something new here compared to PEP 505 and previous `??` discussions:

1. **Scope narrowing** – Previous proposals (like PEP 505) bundled `??` with `?.` and `?[]`, which raised major concerns about hiding `AttributeError` and making debugging harder. My proposal is **only `??` and `??=`** – the safest, most focused subset.

2. **Atomicity** – This is the key new argument. The manual `if x is None: x = default` pattern has a race condition between the check and the assignment. `??=` can be implemented as an atomic operation (check + assign in one step), which is impossible with the current pattern. This matters for shared state in concurrent contexts (threads, asyncio, caches).

3. **The `or` problem remains real** – Python still lacks a concise way to say “use this value, but if it’s `None`, fall back to that one”. Using `or` for this is a common source of bugs when `0`, `False`, or `''` are legitimate values.

Previous discussions rejected the _family_ of operators, not necessarily this single operator on its own. I think it’s worth revisiting the narrow version.

Thank you for holding me to the guidelines – I’ll update the opening post to reference PEP 505 and prior discussions clearly.

---

<div class="post-metadata">

**Author:** ![ronaldoussoren](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/ronaldoussoren/32/61_2.png) [@ronaldoussoren](https://discuss.python.org/u/ronaldoussoren)\
**Post date:** [May 24, 2026, 9:33am UTC](https://discuss.python.org/t/none-coalescing-operator-and-null-coalescing-assignment/107510/6 "2026-05-24T09:33:19Z")

</div>

I have no strong opinion on this, mostly because I don’t particularly need this functionality. The reason I reacted is that there is, a lot of, previous discussions about this functionality that you don’t acknowledge in your initial post.

---

<div class="post-metadata">

**Author:** ![Rainwalker](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/rainwalker/32/35760_2.png) [@Rainwalker](https://discuss.python.org/u/Rainwalker)\
**Post date:** [May 24, 2026, 9:34am UTC](https://discuss.python.org/t/none-coalescing-operator-and-null-coalescing-assignment/107510/7 "2026-05-24T09:34:41Z")

</div>

You’re right – I should have referenced PEP 505 in the opening post. I’ll edit it now to add a clear note. Thanks for the feedback.

---

<div class="post-metadata">

**Author:** ![cdce8p](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/cdce8p/32/3575_2.png) [@cdce8p](https://discuss.python.org/u/cdce8p)\
**Post date:** [May 24, 2026, 9:51am UTC](https://discuss.python.org/t/none-coalescing-operator-and-null-coalescing-assignment/107510/8 "2026-05-24T09:51:37Z")

</div>

Thanks for your post @Rainwalker. May I suggest you catch up on [Revisiting PEP 505 – None-aware operators](https://discuss.python.org/t/revisiting-pep-505-none-aware-operators/74568)? That’s where the last discussion has happened. Would be great if we could keep it all together.

As for the particular idea, I’m working on a PEP for `??` and `??=` already. _You can find a link to an earlier draft at the bottom of that post._ Will need to do at least one more round of rewrites and haven’t had the time to do it yet unfortunately. Hopefully soon though so it can target 3.16.

---

<div class="post-metadata">

**Author:** ![Rainwalker](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/rainwalker/32/35760_2.png) [@Rainwalker](https://discuss.python.org/u/Rainwalker)\
**Post date:** [May 24, 2026, 10:01am UTC](https://discuss.python.org/t/none-coalescing-operator-and-null-coalescing-assignment/107510/9 "2026-05-24T10:01:16Z")

</div>

Thanks for letting me know. I didn’t realize someone was already working on this.

I’ll follow the existing discussion you mentioned. Let me know if I can help — I think the atomicity argument for `??=` is a strong point worth including.

---

<div class="post-metadata">

**Author:** ![elis.byberi](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/elis.byberi/32/35937_2.png) [@elis.byberi](https://discuss.python.org/u/elis.byberi)\
**Post date:** [May 24, 2026, 10:08am UTC](https://discuss.python.org/t/none-coalescing-operator-and-null-coalescing-assignment/107510/10 "2026-05-24T10:08:50Z")

</div>

> [@Rainwalker](#):
>
> Currently, the standard way to provide a default value when something is `None` is:
> 
> ```python
> value = get_value() if get_value() is not None else default
> 
> ```
> 
> This has two major issues

If it has two major issues, it does not make it the standard approach.

This could be the modern approach:

```python
if (value := get_value()) is None:
    value = default

```

Or any explicit `if value is None` check. Alternatively, you can add a default argument in the `get_value()` function if it is expected to return `None`, and so on.

`dict.get()` is a useful precedent for returning a default value, with `None` as the default. In other words, it is better to follow existing patterns for handling missing values.

* * *

If `??` were only used in cases where a default value is already available, that is quite a narrow use case in general, and it is often just as easy to provide a default argument in the function or method.

* * *

It is also useful to study previous discussions on similar ideas. For example, the argument that another language has a given syntax has been refuted many times, not only for this syntax but for others as well.

---

<div class="post-metadata">

**Author:** ![Rainwalker](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/rainwalker/32/35760_2.png) [@Rainwalker](https://discuss.python.org/u/Rainwalker)\
**Post date:** [May 24, 2026, 10:08am UTC](https://discuss.python.org/t/none-coalescing-operator-and-null-coalescing-assignment/107510/11 "2026-05-24T10:08:57Z")

</div>

Thank you for your edit!

---

<div class="post-metadata">

**Author:** ![Rainwalker](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/rainwalker/32/35760_2.png) [@Rainwalker](https://discuss.python.org/u/Rainwalker)\
**Post date:** [May 24, 2026, 10:10am UTC](https://discuss.python.org/t/none-coalescing-operator-and-null-coalescing-assignment/107510/12 "2026-05-24T10:10:48Z")

</div>

Thanks for the feedback.

A few points:

1. `:=` + `if` still requires a temporary variable and two lines. `??` is more concise and avoids the race condition in concurrent contexts.

2. Adding a default parameter isn’t always possible (third-party functions, built-ins, attributes).

3. `dict.get()` is good for dicts, but `??` works uniformly for variables, attributes, subscripts, and expressions.

4. Other languages aren’t the main argument — they just show the concept works and is intuitive.

---

<div class="post-metadata">

**Author:** ![elis.byberi](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/elis.byberi/32/35937_2.png) [@elis.byberi](https://discuss.python.org/u/elis.byberi)\
**Post date:** [May 24, 2026, 10:32am UTC](https://discuss.python.org/t/none-coalescing-operator-and-null-coalescing-assignment/107510/13 "2026-05-24T10:32:07Z")

</div>

> [@Rainwalker](#):
>
> `:=` + `if` still requires a temporary variable and two lines. `??` is more concise and avoids the race condition in concurrent contexts.

What race condition does `:=` suffer that `??` is intended to avoid?

* * *

I would be happy with a builtin:

```python
coalesce(value, default)

```

It is better than saying “value double question mark”. By the way, how should `??` be read?

---

<div class="post-metadata">

**Author:** ![dr\_carlos](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/dr_carlos/32/30461_2.png) [@dr\_carlos](https://discuss.python.org/u/dr_carlos)\
**Post date:** [May 24, 2026, 10:46am UTC](https://discuss.python.org/t/none-coalescing-operator-and-null-coalescing-assignment/107510/14 "2026-05-24T10:46:11Z")

</div>

> [@elis.byberi](#):
>
> By the way, how should `??` be read?

Others will have their own opinions, but I read it as ‘else’:

```python
could_be_none ?? "Default"

```

Reads as “could be none, else default”

---

<div class="post-metadata">

**Author:** ![Rainwalker](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/rainwalker/32/35760_2.png) [@Rainwalker](https://discuss.python.org/u/Rainwalker)\
**Post date:** [May 24, 2026, 11:27am UTC](https://discuss.python.org/t/none-coalescing-operator-and-null-coalescing-assignment/107510/15 "2026-05-24T11:27:43Z")

</div>

**Race condition** : `if (value := get()) is None: value = default` is still two operations (check + assign). Another thread/task could modify `value` between them. `??=` is atomic – the check and assign happen as one step.

**Builtin `coalesce`** : A function can’t short-circuit. With `coalesce(x, expensive_default())`, `expensive_default()` always runs, even when `x` is not `None`. That defeats the purpose.

---

<div class="post-metadata">

**Author:** ![Rainwalker](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/rainwalker/32/35760_2.png) [@Rainwalker](https://discuss.python.org/u/Rainwalker)\
**Post date:** [May 24, 2026, 11:31am UTC](https://discuss.python.org/t/none-coalescing-operator-and-null-coalescing-assignment/107510/16 "2026-05-24T11:31:43Z")

</div>

Thank you for your advice. I like it, but I’d prefer “otherwise”, because “else” is defined.

---

<div class="post-metadata">

**Author:** ![barry-scott](https://avatars.discourse-cdn.com/v4/letter/b/e9c0ed/32.png) [@barry-scott](https://discuss.python.org/u/barry-scott)\
**Post date:** [May 24, 2026, 11:33am UTC](https://discuss.python.org/t/none-coalescing-operator-and-null-coalescing-assignment/107510/17 "2026-05-24T11:33:35Z")

</div>

> [@Rainwalker](#):
>
> Race condition

Is this atomic?

```python
a += 1

```

If not why should ??= be atomic with the side effect of locking overhead?

---

<div class="post-metadata">

**Author:** ![Rainwalker](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/rainwalker/32/35760_2.png) [@Rainwalker](https://discuss.python.org/u/Rainwalker)\
**Post date:** [May 24, 2026, 11:35am UTC](https://discuss.python.org/t/none-coalescing-operator-and-null-coalescing-assignment/107510/18 "2026-05-24T11:35:43Z")

</div>

You’re right – `a += 1` is not atomic (it does a read-modify-write).

The atomicity I’m claiming for `??=` is more limited: it ensures the check for `None` and the assignment (if needed) happen as a single step, without another thread seeing the target in between.

This is still better than the manual `if x is None: x = default`, where the check and assignment are two separate statements. A thread could see `x` as `None` after the check but before the assignment.

No locking overhead – just the same bytecode-level guarantee as `+=`, but applied to the check-then-assign pattern.

---

<div class="post-metadata">

**Author:** ![dr\_carlos](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/dr_carlos/32/30461_2.png) [@dr\_carlos](https://discuss.python.org/u/dr_carlos)\
**Post date:** [May 24, 2026, 11:46am UTC](https://discuss.python.org/t/none-coalescing-operator-and-null-coalescing-assignment/107510/19 "2026-05-24T11:46:36Z")

</div>

Yes, there was a lengthy discussion on this in the PEP 505 thread.  
To be clear, I’m not saying it should be the keyword, just that I read it in my head that way - where there is no syntax or keyword rules 🙂

---

<div class="post-metadata">

**Author:** ![barry-scott](https://avatars.discourse-cdn.com/v4/letter/b/e9c0ed/32.png) [@barry-scott](https://discuss.python.org/u/barry-scott)\
**Post date:** [May 24, 2026, 11:47am UTC](https://discuss.python.org/t/none-coalescing-operator-and-null-coalescing-assignment/107510/20 "2026-05-24T11:47:26Z")

</div>

> [@Rainwalker](#):
>
> No locking overhead – just the same bytecode-level guarantee as `+=`, but applied to the check-then-assign pattern.

Then it not very useful when running multiple threads I assume.

[Next page](https://discuss.python.org/t/none-coalescing-operator-and-null-coalescing-assignment/107510.md?page=2)
