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.
Side-by-side comparison
#The function ends normally
The actual result at runtime
A value of this type can be assigned to a variable
Checking a union’s exhaustiveness
Type of a callback function
Typical use
Example
#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)
}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
#function name(): void { … }
function fail(): never { throw new Error() }More examples
#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' }))Start Stop
const log: (message: string) => void = (message) => {
console.log(message.toUpperCase())
return message.length
}
log('html')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
voidexplicitly to functions that perform a side effect. It tells the reader they shouldn’t use the result. - An
assertNeverfunction in thedefaultbranch is a simple way to make the compiler remind you about new union variants. - Don’t try to declare a variable of type
neverthat you want to assign something to. No value, not evenundefined, matches that type.
Related entries
#Spotted a mistake or missing an example? Write to us.