functions that quietly return nothing

Missing & Undefined Returns

Some path through the function never hits a return, or returns null / undefined / None, and the caller treats that absence as data. Strongly typed languages drag this from a downstream mystery to a compile-time error.

A code path falls off the end, and 'nothing' silently becomes a value.

01in the wild

In the wild

The Same Bug, Solved by Types

Strongly typed languages move the unstructured-state failure from runtime to compile time, where it's cheap.

example.ts
// strictNullChecks turns a runtime crash into a red squiggle
interface User { name: string; age?: number; }

function birthday(u: User): number {
  return u.age + 1;   // compile error: 'u.age' is possibly undefined
}

// RIGHT: the compiler forces you to handle absence
function birthday(u: User): number {
  return (u.age ?? 0) + 1;
}
The optional field can't be used until you account for undefined. The bug never reaches production.
// observed
without guard: TS2532 at compile time
with ??:       compiles, handles missing age
example.go
// Errors are values you must thread through, not exceptions
func current(v, r float64) (float64, error) {
    if r == 0 {
        return 0, fmt.Errorf("resistance is zero")
    }
    return v / r, nil
}

i, err := current(5, 0)
if err != nil { return err }   // the compiler nags if you ignore it
Returning (value, error) makes the failure path part of the signature. Linters flag an ignored err.
// observed
current(5, 0) -> 0, error('resistance is zero')
err is checked before i is used
example.kt
// Nullability is in the type system: String vs String?
fun greet(name: String) = "Hi, $name"

val maybe: String? = lookup()
greet(maybe)          // compile error: String? where String expected

// RIGHT: the safe call / elvis operator handle the null
greet(maybe ?: "stranger")
String and String? are different types. You cannot pass a nullable where a non-null is required without handling it.
// observed
greet(maybe):       compile error
greet(maybe ?: ...): 'Hi, stranger'
example.rs
// Absence is Option<T>; you cannot forget to handle None.
fn first_word(s: &str) -> Option<&str> {
    s.split_whitespace().next()
}

match first_word("") {
    Some(w) => println!("{w}"),
    None    => println!("(empty input)"),   // exhaustive
}
Option<T> makes 'nothing' a value the type system tracks. The match must cover None or it won't compile.
// observed
first_word("") -> None
match is exhaustive: '(empty input)'

The Function That Forgets to Return

A branch with no return doesn't error; it hands back undefined / None and the caller runs with it.

example.js
// BUG: the else path has no return -> undefined
function discount(user) {
  if (user.isMember) return 0.1;
  // forgot the non-member case
}
const price = base * (1 - discount(guest));   // NaN: 1 - undefined

// RIGHT: every path returns; lint with consistent-return
function discount(user) {
  return user.isMember ? 0.1 : 0;
}
A missing return is undefined, and arithmetic on undefined is NaN: a wrong number with no stack trace.
// observed
guest: 1 - undefined -> NaN
fixed: 1 - 0 -> 1
example.py
# BUG: the loop may finish without returning -> implicit None
def find_admin(users):
    for u in users:
        if u.role == "admin":
            return u
    # no admin? falls off the end -> None

admin = find_admin(users)
admin.notify()        # AttributeError: 'NoneType' has no 'notify'

# RIGHT: make the 'not found' case explicit
def find_admin(users):
    return next((u for u in users if u.role == "admin"), None)
Python returns None when execution falls off the end. The caller crashes later, far from the function that 'forgot'.
// observed
no admin: None -> AttributeError downstream
explicit: caller checks for None first
02cross-pollination

Where this compounds

03weakness catalog

Mapped weaknesses (CWE)

On its own, this defect is catalogued by MITRE as one or more of these weaknesses. The exploitable vulnerability usually appears only when it chains or combines with another.