Skip to content

Boolean and Logical

boolean is a primitive union: it is exactly true | false. That makes it the type where distribution and branching options matter most, so the boolean category identifies each of the three types separately. The logical category then operates on boolean results with And, Or, Not and Xor, and the binary category provides the same four operators over a single bit.

Every predicate here follows the same shape, and IsBoolean is the canonical example:

type IsBoolean<T, $O extends $StrictOptions<$O, IsBoolean.$Options> = {}>
namespace IsBoolean {
interface $Options
extends $Selection.Options,
$Distributive.Options,
$Exact.Options,
$InputOptions<$Any | $Unknown | $Never | $Void> {}
type $Default = $Selection.Predicate & $Distributive.Default & $Exact.Default
type $Branch<$O extends $Options = {}>
type $<T, $O extends $Selection.Options & $Distributive.Options & $Exact.Options>
}

Four parts are worth knowing:

  • $Options is what you may pass as the second type argument. $Selection.Options gives you selection: 'predicate' | 'filter' plus the $then/$else branches; $Distributive.Options gives distributive; $Exact.Options gives exact; $InputOptions gives $any, $unknown, $never and $void for the special types.
  • $Default spells out the value each of those options takes when you leave it out. Every predicate with $Options has one. Passing it is the same as passing no options.
  • $Branch replaces the branch results with unique selectors, so a caller can tell exactly which branch was taken. See type branching.
  • $<T, $O> is the internal type util. It skips the special-type handling and is meant for building your own types on top, not for everyday use.
type R1 = IsBoolean<boolean> // true
type R2 = IsBoolean<boolean, { selection: 'filter' }> // boolean
type R3 = IsBoolean<boolean, { $then: 'yes', $else: 'no' }> // 'yes'
type R4 = IsBoolean<any, { $any: 'any' }> // 'any'
type R5 = IsBoolean<boolean, IsBoolean.$Branch> // $Then
type IsBoolean<T, $O extends $StrictOptions<$O, IsBoolean.$Options> = {}>
type IsNotBoolean<T, $O extends $StrictOptions<$O, IsNotBoolean.$Options> = {}>

True for boolean, true and false alike.

type R1 = IsBoolean<boolean> // true
type R2 = IsBoolean<true> // true
type R3 = IsBoolean<false> // true
type R4 = IsBoolean<number> // false
type R5 = IsBoolean<string | boolean> // boolean

Filtering keeps the boolean part of a union, and disabling distribution evaluates the union as a whole:

type R1 = IsBoolean<string | boolean, { selection: 'filter' }> // boolean
type R2 = IsNotBoolean<string | boolean, { selection: 'filter' }> // string
type R3 = IsBoolean<boolean | 1> // boolean
type R4 = IsBoolean<boolean | 1, { distributive: false }> // false
type IsTrue<T, $O extends $StrictOptions<$O, IsTrue.$Options> = {}>
type IsFalse<T, $O extends $StrictOptions<$O, IsFalse.$Options> = {}>
type IsNotTrue<T, $O extends $StrictOptions<$O, IsNotTrue.$Options> = {}>
type IsNotFalse<T, $O extends $StrictOptions<$O, IsNotFalse.$Options> = {}>

These check the exact literal. Because boolean is true | false and the check distributes, feeding it boolean takes both branches and yields boolean:

type R1 = IsTrue<true> // true
type R2 = IsTrue<false> // false
type R3 = IsTrue<boolean> // boolean
type R4 = IsTrue<number> // false
type R5 = IsFalse<false> // true
type R6 = IsNotTrue<false> // true
type R7 = IsNotFalse<true> // true

Filtering narrows a union down to the literal you asked for:

type R1 = IsTrue<boolean, { selection: 'filter' }> // true
type R2 = IsTrue<string | boolean, { selection: 'filter' }> // true
type R3 = IsNotTrue<string | boolean, { selection: 'filter' }> // string | false

With $Branch, boolean reports both branches, which is how you detect the ambiguous case:

type R1 = IsTrue<true, IsTrue.$Branch> // $Then
type R2 = IsTrue<boolean, IsTrue.$Branch> // $Then | $Else
type R3 = IsTrue<string, IsTrue.$Branch> // $Else
type And<A extends boolean, B extends boolean, $O extends $StrictOptions<$O, $Selection.$BaseOptions> = {}>
type Or<A extends boolean, B extends boolean, $O extends $StrictOptions<$O, $Selection.$BaseOptions> = {}>
type Not<X extends boolean, $O extends $StrictOptions<$O, $Selection.$BaseOptions> = {}>
type Xor<A extends boolean, B extends boolean, $O extends $StrictOptions<$O, $Selection.$BaseOptions> = {}>

The logic operators. Inputs are constrained to boolean, so they compose directly with the predicates above.

type R1 = And<true, true> // true
type R2 = And<true, false> // false
type R3 = Or<true, false> // true
type R4 = Or<false, false> // false
type R5 = Not<true> // false
type R6 = Not<false> // true
type R7 = Xor<true, false> // true
type R8 = Xor<true, true> // false

They take $Selection.$BaseOptions, the $then/$else half of the selection options, so a result can be mapped straight to your own values:

type R = And<IsString<'a'>, IsNumber<1>, { $then: 'both', $else: 'not both' }> // 'both'

Note that Xor only forwards $O when A is false; when A is true it delegates to Not<B> with the default branches.

namespace Bit {
type Bit = 0 | 1
type Not<X extends Bit>
type And<A extends Bit, B extends Bit>
type Or<A extends Bit, B extends Bit>
type Xor<A extends Bit, B extends Bit>
}

🏷️ since 8.0.0

The binary category is the same four operators over 0 | 1 instead of true | false, under the Bit namespace.

import type { Bit } from 'type-plus'
type R = Bit.And<1, 1> // 1
type R = Bit.Or<0, 1> // 1
type R = Bit.Not<0> // 1
type R = Bit.Xor<1, 1> // 0
type R = Bit.Bit // 0 | 1

🗑️ removed in 8.0.0: the B namespace, the same namespace under a shorter name — use Bit. B is the name most often given to a type parameter, and a type parameter B shadows the namespace inside any generic that declares one.

Reach for these when the type-level code around them already carries 0 | 1, so the result stays a number instead of being converted back from a boolean. When it feeds a predicate, use the boolean operators above.

Unlike those, the bit operators take no $Options: they are building blocks, with no branches to override.

Type Description
IsBoolean / IsNotBoolean T is (not) boolean, true or false
IsTrue / IsNotTrue T is (not) exactly true
IsFalse / IsNotFalse T is (not) exactly false
And<A, B, $O> logical and
Or<A, B, $O> logical or
Not<X, $O> logical not
Xor<A, B, $O> logical exclusive or
Bit the same four operators over 0 | 1 (B was removed in 8.0.0)

See Options for selection, distributive and exact.

Source: src/boolean, src/logical and src/binary.