ComparisonComparisons

void vs never

void means a function returns no useful value, while never means a function never returns at all: it throws an error or runs forever.

On this page

Side-by-side comparison

#
  • The function ends normally

    void
    Yes
    never
    No, it throws an error or runs forever
  • The actual result at runtime

    void
    undefined
    never
    None, the function never returns
  • A value of this type can be assigned to a variable

    void
    Yes, only undefined
    never
    No, no value matches
  • Checking a union’s exhaustiveness

    void
    Doesn’t express this
    never
    Yes, in the default branch
  • Type of a callback function

    void
    Loose: also accepts a function that returns something
    never
    Rarely used in this role
  • Typical use

    void
    Functions with no useful result, event handlers
    never
    Functions that throw, assertNever, endless loops

Example

#
TypeScript
function logProgress(done: number, total: number): void {
  console.log(`Progress: ${Math.round((done / total) * 100)}%`)
}

function fail(message: string): never {
  throw new Error(message)
}

const result = logProgress(3, 4)
console.log(result)

try {
  fail('something went wrong')
} catch (error) {
  console.log((error as Error).message)
}
Result
Progress: 75%
undefined
something went wrong

Definition and usage

#

Both types most often describe a function’s return value, and you use them where an ordinary value wouldn’t make sense. Neither one lets you do anything meaningful with the result afterward.

The difference is fundamental. A function typed void ends normally, it simply returns nothing useful, and in practice its result is undefined. A function typed never never returns to the call site at all: it either throws an exception or runs in an infinite loop. never also shows up as the type of values that can’t occur, e.g. in a default branch after every union variant has been handled, which void never expresses.

Add the type void to functions that perform an effect, e.g. logging, saving, or changing state, and whose result no one should use. Leave the type never for helper functions that always throw, and for checking a union’s exhaustiveness in a switch. Never use never as the type of a variable you want to assign something to, because no value matches it.

Syntax

#
Syntax
function name(): void { … }
function fail(): never { throw new Error() }

More examples

#
never when checking a union’s exhaustiveness
TypeScript
type Command = { kind: 'start' } | { kind: 'stop' }

function assertNever(value: never): never {
  throw new Error(`Unknown command: ${JSON.stringify(value)}`)
}

function run(command: Command): string {
  switch (command.kind) {
    case 'start':
      return 'Start'
    case 'stop':
      return 'Stop'
    default:
      return assertNever(command)
  }
}

console.log(run({ kind: 'start' }))
console.log(run({ kind: 'stop' }))
Result
Start
Stop
void as a return type is looser than never
TypeScript
const log: (message: string) => void = (message) => {
  console.log(message.toUpperCase())
  return message.length
}

log('html')
Result
HTML

Even though the function you pass actually returns something, the type (message: string) => void tells you to ignore it. That’s why functions like arr.forEach(x => list.push(x)) are valid, even though push() returns a number.

Good practices

#
  • Add void explicitly to functions that perform a side effect. It tells the reader they shouldn’t use the result.
  • An assertNever function in the default branch is a simple way to make the compiler remind you about new union variants.
  • Don’t try to declare a variable of type never that you want to assign something to. No value, not even undefined, matches that type.

Related entries

#

Spotted a mistake or missing an example? Write to us.