<p>There seem to be two main ways that languages handle the same concept:</p>
<ul>
<li>Nullable types, like Kotlin, C#, and many other OOP languages</li>
<li><code>Option</code>s, like Rust and Haskell, which are wrappers around a type that can either be <code>Some(...)</code> or <code>None</code> (or equivalent names)</li>
</ul>
<p>While these serve similar purposes, they do it in different ways. Having used and liked languages with both of these, what are the pros and cons of each?</p>

<nullability><optionals>
What are the pros and cons of an Option type in comparison to nullable types?
<h2>Nullable types are at the same level of indirection</h2>
<p>Suppose I have the following Kotlin code:</p>
<pre class="lang-kotlin prettyprint-override"><code>fun foo(): Bar? {...}

val x = foo()
if (x != null) {
    doStuff(x)
}
</code></pre>
<p>If I later decide that <code>Bar</code> doesn't need to be nullable (perhaps because an external API changed) then I can simply remove the <code>?</code> from the return type. While the users of my function now have no reason to do the null check, their code still compiles, albeit with a &quot;redundant code&quot; warning. If Kotlin had opted to use an <code>Optional</code> type instead, the users of my function can't recompile their code without changing it.</p>
<p>Credit to <a href="https://news.ycombinator.com/item?id=24126444" rel="noreferrer">this HN post</a> I saw yesterday for the basic idea.</p>

CC BY-SA 4.0
2023-05-23T16:55:48.333Z
2023-05-23T17:14:06.380Z
2023-05-23T17:14:06.380Z
Seggan
CC BY-SA 4.0
2023-05-23T16:58:08.817Z
Swift allows implicit wrapping, so `5 == nil` is equivalent to `.some(5) == nil`. You still get the redundant check warning, but it’s legal (unlike in Rust, tmk).
Bbrk24
CC BY-SA 4.0
2023-05-23T17:12:37.050Z
What I meant by this is that in a language like Java where everything is a reference type, `Optional` is just a pointer to a pointer, which is totally unnecessary when you could just use a single pointer or null.
Bbrk24
CC BY-SA 4.0
2023-05-23T17:13:39.680Z
@Bbrk24 feel free to add as an answer then
Seggan
<p><strong>Monad</strong></p>
<p>It should be noted that usage of option types can be simplified by using monad and other functional programming facilities, and they do not need to be unwrapped everywhere.</p>
<p>For example, if you have two option values <code>mx</code> and <code>my</code> of <code>Maybe Integer</code>, you don't have to unwrap them and can add them using the <code>do</code> notation with monads:</p>
<pre><code>do x &lt;- mx
   y &lt;- my
   pure (x + y)
</code></pre>
<p>If one of <code>mx</code> and <code>my</code> is <code>Nothing</code>, the return value will be <code>Nothing</code>. There is no need to case analyze whether <code>mx</code> and <code>my</code> are empty or not.</p>
<p>With the <code>Applicative</code> interface, one can also add the option values &quot;directly&quot;:</p>
<pre><code>pure (+) &lt;*&gt; mx &lt;*&gt; my
</code></pre>
<p>So, the syntactic burden of option types isn't necessarily heavy.</p>
<p><strong>NULL</strong></p>
<p>On the other hand, <code>NULL</code> values have many problems as already mentioned in other answers.</p>
<p>One good example is the SQL language. There is much ambiguity as to how NULLs should be handled or even interpreted. To this day, there is still <a href="https://dba.stackexchange.com/q/258642/55439">inconsistencies among SQL aggregate functions</a> as to whether they should return a NULL if the input is empty. Some return NULL (e.g. array_agg()) while others do not (e.g. count()).</p>
<p>Similarly, in practice, some users enter an empty string <code>''</code> while others enter a <code>NULL</code> when a string field has no data.</p>

CC BY-SA 4.0
2023-05-25T10:31:52.117Z
2023-05-25T10:31:52.117Z
tinlyx
<p>Apart from @user's answer, there's usually the following convention for nullable types:</p>
<pre><code>a?.b()?.c()
</code></pre>
<p>This it is intuitive to translate the above into</p>
<pre><code>if (a != null) {
  b = a.b()
  if (b != null) {
    c = b.c()
    ....
</code></pre>
<p>However, it may also be the case that <code>b()</code> returns non-null, so the second null check is unnecessary. However, we may not write <code>a?.b().c()</code> because <code>a?.b()</code> as an expression is nullable!</p>
<p>This is not a problem in chained <code>Option</code> operations.</p>

CC BY-SA 4.0
2023-05-17T15:45:53.193Z
2023-05-17T15:45:53.193Z
ice1000
CC BY-SA 4.0
2023-05-17T23:11:50.260Z
In some languages like C#, you can do `a?.b().c()`, as the `?.` eats up the whole rest of the expression.
naffetS
CC BY-SA 4.0
2024-06-17T05:48:41.617Z
Just a heads up that this answer is surprisingly hard to read if one isn’t familiar with the `?.` operator. I started out reading it as `a?` `.` `b()?` `.` `c()`, got confused by this, frantically searched for users's answer, realised it didn’t talk about any `x?.y` at all; only on revisiting this answer did it click that `?.` is *one* operator here. Perhaps calling that out with an introductory sentence that names the operator and language would be helpful.
MisterMiyagi
<h1>Operators</h1>
<p>Usually, nullable types and <code>Option</code>s will provide special operators, like the Elvis (<code>?:</code>), for checking if they're null/asserting that they're not null/etc. <code>Option</code> types allow this to be implemented in a consistent manner within the language and easily overridden by other types, since they can just be implemented as operator functions using whatever syntax the language uses for those.</p>

CC BY-SA 4.0
2023-05-17T15:42:31.707Z
2023-05-17T15:42:31.707Z
Ginger
<h1>Special-casing</h1>
<p>Another factor to consider is how much of a special case your approach to nullability will require. <code>Option</code> almost never requires any special handling in the compiler, as it can be defined as a simple datatype within the confines of the language.</p>
<p>On the other hand, nullable types can sometimes require a new specialized type within the type system. If Kotlin didn't have nullable types and everything was non-<code>null</code> forever, we wouldn't be able to ergonomically add nullable types to the language without modifying the compiler. Note that this isn't true in languages with union types, as &quot;nullable string&quot; can be written in a language like Typescript simply as <code>string | null</code>.</p>

CC BY-SA 4.0
2023-05-17T15:45:10.033Z
2023-05-17T15:45:10.033Z
Silvio Mayolo
CC BY-SA 4.0
2023-05-17T16:13:15.383Z
It’s worth noting that special casing is not *that* heavy to implement. In my language with nullable types the actual handling for them is just over 2 lines of code
Seggan
CC BY-SA 4.0
2023-05-17T16:20:01.100Z
@Seggan Does your language have flow typing, though? That's a lot more complicated (although flow typing is certainly worth it)
user
CC BY-SA 4.0
2023-05-17T16:33:41.387Z
@user yes it will. I don’t see how it’s that different from doing SSA conversions tho
Seggan
<p><code>Option</code> is an <a href="https://en.wikipedia.org/wiki/Algebraic_data_type" rel="noreferrer">algebraic data type (ADT)</a>, so if your language already supports ADTs, you can implement <code>Option</code> in the standard library, whereas nullable types are implemented as a separate feature. <code>Option</code> may be passed as a type parameter because it allows nesting (e.g. <code>Option&lt;Option&lt;T&gt;&gt;</code>), whereas for nullable types, that is not necessarily the case.</p>
<p>On the other hand, <code>Option</code>s usually have to be unwrapped explicitly using <a href="https://en.wikipedia.org/wiki/Pattern_matching" rel="noreferrer">pattern matching</a>, so nullable types may be more syntactically lightweight to use. This can be mitigated by adding syntactic sugar like Rust's <code>?</code> operator.</p>

CC BY-SA 4.0
2023-05-17T15:49:09.523Z
2023-05-17T16:08:39.747Z
2023-05-17T16:08:39.747Z
James Martin
CC BY-SA 4.0
2023-05-17T15:56:29.200Z
Why would you ever need a nested option type?
Seggan
CC BY-SA 4.0
2023-05-17T15:57:54.197Z
@Seggan Suppose you have some generic function working on lists of any type `T`, and you return a list of `Option<T>`s. What if `T = Option<Int>`?
user
CC BY-SA 4.0
2023-05-17T16:00:03.250Z
True. I haven’t thought of that
Seggan
CC BY-SA 4.0
2024-06-17T16:00:47.923Z
In the original design for C# 2 `Nullable<Nullable<int>>` aka `int??` was legal; making it illegal late in the ship cycle was the choice of the runtime team. That led to a number of bugs in the compiler which I then had to fix much later. Good times.
Eric Lippert
<p>Advantages of <code>Option</code> types:</p>
<ul>
<li>No need to implement flow typing, which can be hard to get right</li>
<li>Can be used with primitive types (<code>Int?</code> would require boxing in a language with nullable types, removing the performance benefits)</li>
<li>Can be nested - A function can accept an <code>Option&lt;T&gt;</code>, where <code>T</code> may itself be <code>Option&lt;Foo&gt;</code>. With boxing, it's possible to distinguish between a <code>Some(None)</code> and a <code>None</code> in this case. This isn't possible with nullable types (<code>Int??</code> isn't a thing in Kotlin).</li>
<li>Can implement interfaces such as <code>Iterable</code> or typeclasses such as <code>Monad</code></li>
</ul>
<p>Advantages of nullable types:</p>
<ul>
<li>Syntactically lighter - It can be cumbersome to write <code>Option&lt;T&gt;</code> and <code>Some(x)</code> everywhere, while in a language like Kotlin, you only need to write <code>T?</code> and <code>x</code>. Note: alephalpha pointed out in the comments that this isn't always true - the syntax for Zig's optional types looks similar to that of nullable types in other languages.</li>
<li>With flow typing, once you check that a value is not null, you don't need to check again (in most circumstances). That means you can do <code>if (x != null) x.foo(x.bar)</code> rather than <code>if (x.nonEmpty) x.get.foo(x.get.bar)</code> or <code>x.map(y =&gt; y.foo(y.bar))</code></li>
<li>No boxing means that they take up less space and there's a performance benefit since you don't need to unwrap a <code>Some</code>/<code>Just</code> every time you want to access the value inside. As Bbrk24 mentioned in the comments, though, languages such as Rust and Swift can implement Options without boxing or with only a single bit to indicate whether the Option is empty, so this point only applies to certain languages. For example, Scala boxes its <code>Option</code>s (although it's <a href="https://github.com/sjrd/scala-unboxed-option" rel="nofollow noreferrer">entirely possible</a> to have nestable unboxed Options even in a language that doesn't natively support them, depending on your language's type system).</li>
</ul>

CC BY-SA 4.0
2023-05-17T15:36:12.437Z
2024-06-16T14:56:50.970Z
2024-06-16T14:56:50.970Z
user
CC BY-SA 4.0
2023-05-17T16:11:14.727Z
The lighter syntax of nullable types is why I implemented them over option types in my language
Seggan
CC BY-SA 4.0
2023-05-17T16:12:25.207Z
@Bbrk24: GHC Haskell doesn’t have any special representation for `Maybe T` vs. `T`. A lazy `Maybe` value (i.e., not a thunk) is a pointer to a tagged union, which is either `Nothing` or `Just (x :: T)` where `x` may be a thunk. As an optimisation, we can tell whether it’s `Nothing` or `Just` without dereferencing that pointer, by examining its tag bits, since the data type has a small number of constructors. A strict `Maybe` (from the `strict` package) contains a pointer instead of a thunk in the `Just` case.
Jon Purdy
CC BY-SA 4.0
2023-05-17T16:17:28.967Z
Either options or nullable types can be implemented with(out) boxes as an optimization. The intuitive implementation of a type isn't an intrinsic property of that type, so I'd avoid designing a language around it.
James Martin
CC BY-SA 4.0
2024-06-14T04:06:12.970Z
[Zig's optional type](https://ziglang.org/documentation/master/#Optionals) uses a light syntax.
alephalpha
CC BY-SA 4.0
2024-06-15T14:28:52.693Z
@alephalpha Correct me if I'm wrong, but aren't Zig's optionals like Kotlin's nullable types, rather than what I mean by "optional" in this answer?
user
CC BY-SA 4.0
2024-06-16T02:10:43.857Z
@user Zig's optional type can be nested. You can write something like `??i32`. `const a: ??i32 = null;` and `const b: ?i32 = null; const c: ??i32 = b;` are valid Zig code, and `a` and `c` are different values. Internally, optional types are represented as tagged unions, and like Rust, the [null pointer optimization](https://stackoverflow.com/q/46557608/2756937) is used when the optional type is a pointer.
alephalpha
CC BY-SA 4.0
2024-06-16T14:55:20.697Z
@alephalpha Ah ok, interesting
user
# What are the pros and cons of an Option type in comparison to nullable types?
Posted by: username_0 on 2023-05-17
There seem to be two main ways that languages handle the same concept:

* Nullable types, like Kotlin, C#, and many other OOP languages
* `Option`s, like Rust and Haskell, which are wrappers around a type that can either be `Some(...)` or `None` (or equivalent names)

While these serve similar purposes, they do it in different ways. Having used and liked languages with both of these, what are the pros and cons of each?
**Tags:** nullability optionals
---
## 7 answers
### Answer by username_9 on 2023-05-17. Score: 18
Advantages of `Option` types:

* No need to implement flow typing, which can be hard to get right
* Can be used with primitive types (`Int?` would require boxing in a language with nullable types, removing the performance benefits)
* Can be nested - A function can accept an `Option`, where `T` may itself be `Option`. With boxing, it's possible to distinguish between a `Some(None)` and a `None` in this case. This isn't possible with nullable types (`Int??` isn't a thing in Kotlin).
* Can implement interfaces such as `Iterable` or typeclasses such as `Monad`

Advantages of nullable types:

* Syntactically lighter - It can be cumbersome to write `Option` and `Some(x)` everywhere, while in a language like Kotlin, you only need to write `T?` and `x`. Note: alephalpha pointed out in the comments that this isn't always true - the syntax for Zig's optional types looks similar to that of nullable types in other languages.
* With flow typing, once you check that a value is not null, you don't need to check again (in most circumstances). That means you can do `if (x != null) x.foo(x.bar)` rather than `if (x.nonEmpty) x.get.foo(x.get.bar)` or `x.map(y => y.foo(y.bar))`
* No boxing means that they take up less space and there's a performance benefit since you don't need to unwrap a `Some`/`Just` every time you want to access the value inside. As Bbrk24 mentioned in the comments, though, languages such as Rust and Swift can implement Options without boxing or with only a single bit to indicate whether the Option is empty, so this point only applies to certain languages. For example, Scala boxes its `Option`s (although it's [entirely possible](https://github.com/sjrd/scala-unboxed-option) to have nestable unboxed Options even in a language that doesn't natively support them, depending on your language's type system).
> **Comments:**
> **username_1** (2023-05-17): The lighter syntax of nullable types is why I implemented them over option types in my language  
> **username_12** (2023-05-17): @Bbrk24: GHC Haskell doesn’t have any special representation for `Maybe T` vs. `T`. A lazy `Maybe` value (i.e., not a thunk) is a pointer to a tagged union, which is either `Nothing` or `Just (x :: T)` where `x` may be a thunk. As an optimisation, we can tell whether it’s `Nothing` or `Just` without dereferencing that pointer, by examining its tag bits, since the data type has a small number of constructors. A strict `Maybe` (from the `strict` package) contains a pointer instead of a thunk in the `Just` case.  
> **username_10** (2023-05-17): Either options or nullable types can be implemented with(out) boxes as an optimization. The intuitive implementation of a type isn't an intrinsic property of that type, so I'd avoid designing a language around it.  
> **username_13** (2024-06-14): [Zig's optional type](https://ziglang.org/documentation/master/#Optionals) uses a light syntax.  
> **username_9** (2024-06-15): @alephalpha Correct me if I'm wrong, but aren't Zig's optionals like Kotlin's nullable types, rather than what I mean by "optional" in this answer?  
> **username_13** (2024-06-16): @user Zig's optional type can be nested. You can write something like `??i32`. `const a: ??i32 = null;` and `const b: ?i32 = null; const c: ??i32 = b;` are valid Zig code, and `a` and `c` are different values. Internally, optional types are represented as tagged unions, and like Rust, the [null pointer optimization](https://stackoverflow.com/q/46557608/2756937) is used when the optional type is a pointer.  
> **username_9** (2024-06-16): @alephalpha Ah ok, interesting  
---
### Answer by username_8 on 2023-05-17. Score: 12
# Special-casing

Another factor to consider is how much of a special case your approach to nullability will require. `Option` almost never requires any special handling in the compiler, as it can be defined as a simple datatype within the confines of the language.

On the other hand, nullable types can sometimes require a new specialized type within the type system. If Kotlin didn't have nullable types and everything was non-`null` forever, we wouldn't be able to ergonomically add nullable types to the language without modifying the compiler. Note that this isn't true in languages with union types, as "nullable string" can be written in a language like Typescript simply as `string | null`.
> **Comments:**
> **username_1** (2023-05-17): It’s worth noting that special casing is not *that* heavy to implement. In my language with nullable types the actual handling for them is just over 2 lines of code  
> **username_9** (2023-05-17): @Seggan Does your language have flow typing, though? That's a lot more complicated (although flow typing is certainly worth it)  
> **username_1** (2023-05-17): @user yes it will. I don’t see how it’s that different from doing SSA conversions tho  
---
### Answer by username_10 on 2023-05-17. Score: 9
`Option` is an [algebraic data type (ADT)](https://en.wikipedia.org/wiki/Algebraic_data_type), so if your language already supports ADTs, you can implement `Option` in the standard library, whereas nullable types are implemented as a separate feature. `Option` may be passed as a type parameter because it allows nesting (e.g. `Option>`), whereas for nullable types, that is not necessarily the case.

On the other hand, `Option`s usually have to be unwrapped explicitly using [pattern matching](https://en.wikipedia.org/wiki/Pattern_matching), so nullable types may be more syntactically lightweight to use. This can be mitigated by adding syntactic sugar like Rust's `?` operator.
> **Comments:**
> **username_1** (2023-05-17): Why would you ever need a nested option type?  
> **username_9** (2023-05-17): @Seggan Suppose you have some generic function working on lists of any type `T`, and you return a list of `Option<T>`s. What if `T = Option<Int>`?  
> **username_1** (2023-05-17): True. I haven’t thought of that  
> **username_11** (2024-06-17): In the original design for C# 2 `Nullable<Nullable<int>>` aka `int??` was legal; making it illegal late in the ship cycle was the choice of the runtime team. That led to a number of bugs in the compiler which I then had to fix much later. Good times.  
---
### Answer by username_1 on 2023-05-23. Score: 6
Nullable types are at the same level of indirection
Suppose I have the following Kotlin code:
fun foo(): Bar? {...}

val x = foo()
if (x != null) {
    doStuff(x)
}

If I later decide that Bar doesn't need to be nullable (perhaps because an external API changed) then I can simply remove the ? from the return type. While the users of my function now have no reason to do the null check, their code still compiles, albeit with a "redundant code" warning. If Kotlin had opted to use an Optional type instead, the users of my function can't recompile their code without changing it.
Credit to this HN post I saw yesterday for the basic idea.
> **Comments:**
> **username_2** (2023-05-23): Swift allows implicit wrapping, so `5 == nil` is equivalent to `.some(5) == nil`. You still get the redundant check warning, but it’s legal (unlike in Rust, tmk).  
> **username_2** (2023-05-23): What I meant by this is that in a language like Java where everything is a reference type, `Optional` is just a pointer to a pointer, which is totally unnecessary when you could just use a single pointer or null.  
> **username_1** (2023-05-23): @Bbrk24 feel free to add as an answer then  
---
### Answer by username_3 on 2023-05-25. Score: 6
Monad
It should be noted that usage of option types can be simplified by using monad and other functional programming facilities, and they do not need to be unwrapped everywhere.
For example, if you have two option values mx and my of Maybe Integer, you don't have to unwrap them and can add them using the do notation with monads:
do x <- mx
   y <- my
   pure (x + y)

If one of mx and my is Nothing, the return value will be Nothing. There is no need to case analyze whether mx and my are empty or not.
With the Applicative interface, one can also add the option values "directly":
pure (+) <*> mx <*> my

So, the syntactic burden of option types isn't necessarily heavy.
NULL
On the other hand, NULL values have many problems as already mentioned in other answers.
One good example is the SQL language. There is much ambiguity as to how NULLs should be handled or even interpreted. To this day, there is still inconsistencies among SQL aggregate functions as to whether they should return a NULL if the input is empty. Some return NULL (e.g. array_agg()) while others do not (e.g. count()).
Similarly, in practice, some users enter an empty string '' while others enter a NULL when a string field has no data.
---
### Answer by username_4 on 2023-05-17. Score: 6
Apart from @user's answer, there's usually the following convention for nullable types:
a?.b()?.c()

This it is intuitive to translate the above into
if (a != null) {
  b = a.b()
  if (b != null) {
    c = b.c()
    ....

However, it may also be the case that b() returns non-null, so the second null check is unnecessary. However, we may not write a?.b().c() because a?.b() as an expression is nullable!
This is not a problem in chained Option operations.
> **Comments:**
> **username_5** (2023-05-17): In some languages like C#, you can do `a?.b().c()`, as the `?.` eats up the whole rest of the expression.  
> **username_6** (2024-06-17): Just a heads up that this answer is surprisingly hard to read if one isn’t familiar with the `?.` operator. I started out reading it as `a?` `.` `b()?` `.` `c()`, got confused by this, frantically searched for users's answer, realised it didn’t talk about any `x?.y` at all; only on revisiting this answer did it click that `?.` is *one* operator here. Perhaps calling that out with an introductory sentence that names the operator and language would be helpful.  
---
### Answer by username_7 on 2023-05-17. Score: 5
# Operators

Usually, nullable types and `Option`s will provide special operators, like the Elvis (`?:`), for checking if they're null/asserting that they're not null/etc. `Option` types allow this to be implemented in a consistent manner within the language and easily overridden by other types, since they can just be implemented as operator functions using whatever syntax the language uses for those.
---
