Skip to content

Instantly share code, notes, and snippets.

@5cover
Last active March 29, 2026 13:01
Show Gist options
  • Select an option

  • Save 5cover/da8d98fbdc162b8a15696fda80e75d3e to your computer and use it in GitHub Desktop.

Select an option

Save 5cover/da8d98fbdc162b8a15696fda80e75d3e to your computer and use it in GitHub Desktop.
Typing `typeof`: Typeof<T>
import type { Typeof } from './typeof';
type TypeofString = TypeofStringDefined | 'undefined';
type TypeofStringDefined = 'object' | 'function' | 'string' | 'number' | 'bigint' | 'boolean' | 'symbol';
type Recursive = { next: Recursive };
type RecursiveTest = Test<Same<Typeof<Recursive>, 'object'>>;
type Conditional<T> = T extends string ? number : boolean;
type TestConditional = Test<Same<Typeof<Conditional<string | number>>, 'number' | 'boolean'>>;
declare const u: unique symbol;
type TestUniqueSymbol = Test<Same<Typeof<typeof u>, 'symbol'>>;
enum Enum {
A,
}
type TestEnum = Test<Same<Typeof<Enum>, 'number'>> | Test<Same<Typeof<typeof Enum>, 'object'>>;
type EmptyMapped<T> = { [K in keyof T]: never };
type TestEmptyMapped = Test<Same<Typeof<EmptyMapped<{}>>, TypeofStringDefined>>;
type Overload = {
(x: string): number;
(x: number): string;
};
type TestOverload = Test<Same<Typeof<Overload>, 'function'>>;
type Tests =
| Test<Same<Typeof<{ [k: string]: any }>, 'object'|'function'>>
| Test<Same<Typeof<string & never>, never>>
| Test<Same<Typeof<never | string>, 'string'>>
| Test<Same<Typeof<string & { length: number }>, 'string' | 'object'>>
| Test<Same<Typeof<string & number>, never>>
| Test<Same<Typeof<string & { __brand?: 'x' }>, 'string' | 'object'>>
| Test<Same<Typeof<any & string>, TypeofString>>
| Test<Same<Typeof<unknown & string>, 'string'>>
| Test<Same<Typeof<[1, 2, 3]>, 'object'>>
| Test<Same<Typeof<readonly [1, 2]>, 'object'>>
| Test<Same<Typeof<`a${number}`>, 'string'>>
| Test<Same<Typeof<Uppercase<'abc'>>, 'string'>>
| Test<Same<Typeof<'' | 0>, 'string' | 'number'>>
| Test<Same<Typeof<'a' | 'b'>, 'string'>>
| Test<Same<Typeof<(() => void) | null>, 'object' | 'function'>>
| Test<Same<Typeof<{ (): number }>, 'function'>>
| Test<Same<Typeof<((x: number) => string) & { foo: 1 }>, 'function'>>
| Test<Same<Typeof<((x: number) => string) & Record<string, never>>, 'function'>>
| Test<Same<Typeof<((x: number) => void) & { [k: string]: never }>, 'function'>>
| Test<Same<Typeof<[]>, 'object'>>
| Test<Same<Typeof<[1, 2, 3]>, 'object'>>
| Test<Same<Typeof<{ [k in 'a' | 'b']: number }>, 'object'>>
| Test<Same<Typeof<{ [k in 1 | 2]: number }>, 'object'>>
| Test<Same<Typeof<{ [K in never]: never }>, TypeofStringDefined>>
| Test<Same<Typeof<{ [k in symbol]: number }>, 'object'>>
| Test<Same<Typeof<{ [k: number]: string }>, 'object'|'string'>>
| Test<Same<Typeof<{ [k: number]: string; length: number }>, 'object'|'string'>>
| Test<Same<Typeof<{ [k: string]: never } | (() => void)>, 'object' | 'function'>>
| Test<Same<Typeof<{ [k: string]: string }>, 'object'>>
| Test<Same<Typeof<{ [k: string]: string | number; fixed: 1 }>, 'object'>>
| Test<Same<Typeof<{ [k: symbol]: number }>, 'object'>>
| Test<Same<Typeof<{ a: 1 } & { b: 2 }>, 'object'>>
| Test<Same<Typeof<{ a: 1 } | (() => void)>, 'object' | 'function'>>
| Test<Same<Typeof<{ a: 1 } | { b: 2 }>, 'object'>>
| Test<Same<Typeof<{ a: 1 } | null>, 'object'>>
| Test<Same<Typeof<{ a: 1 } | undefined>, 'object' | 'undefined'>>
| Test<Same<Typeof<{ a: 1; b?: 2 }>, 'object'>>
| Test<Same<Typeof<{ a?: 1 }>, TypeofStringDefined>>
| Test<Same<Typeof<{ foo: string } & ((x: number) => string)>, 'function'>>
| Test<Same<Typeof<{ items: string[] }>, 'object'>>
| Test<Same<Typeof<{ readonly a: 1 }>, 'object'>>
| Test<Same<Typeof<{ readonly id: string; name: string }>, 'object'>>
| Test<Same<Typeof<{ type: 'ok' }>, 'object'>>
| Test<Same<Typeof<{ type: 'ok'; value: 123 }>, 'object'>>
| Test<Same<Typeof<{ user: { id: string } }>, 'object'>>
| Test<Same<Typeof<{} & { a: 1 }>, 'object'>>
| Test<Same<Typeof<{} & string>, 'string'>>
| Test<Same<Typeof<{} | { a: 1 }>, TypeofStringDefined>>
| Test<Same<Typeof<{} | null>, TypeofStringDefined>>
| Test<Same<Typeof<{} | Record<string, never>>, TypeofStringDefined>>
| Test<Same<Typeof<{} | undefined>, TypeofStringDefined | 'undefined'>>
| Test<Same<Typeof<{}>, TypeofStringDefined>>
| Test<Same<Typeof<1 | 2 | 3>, 'number'>>
| Test<Same<Typeof<1n | 2n>, 'bigint'>>
| Test<Same<Typeof<abstract new (...args: any[]) => {}>, 'function'>>
| Test<Same<Typeof<any>, TypeofString>>
| Test<Same<Typeof<Array<{ id: string }>>, 'object'>>
| Test<Same<Typeof<bigint>, 'bigint'>>
| Test<Same<Typeof<BigInt>, 'bigint'|'object'>>
| Test<Same<Typeof<boolean>, 'boolean'>>
| Test<Same<Typeof<Boolean>, 'boolean' | 'object'>>
| Test<Same<Typeof<ConstructorParameters<new (x: number) => Date>>, 'object'>>
| Test<Same<Typeof<Date>, 'object'>>
| Test<Same<Typeof<Error | (() => void)>, 'function' | 'object'>>
| Test<Same<Typeof<Error>, 'object'>>
| Test<Same<Typeof<Exclude<unknown, null | undefined>>, TypeofString>>
| Test<Same<Typeof<Function | { a: 1 }>, 'object' | 'function'>>
| Test<Same<Typeof<Function | string>, 'string' | 'object' | 'function'>>
| Test<Same<Typeof<InstanceType<new () => Date>>, 'object'>>
| Test<Same<Typeof<keyof { a: 1; b: 2 }>, 'string'>>
| Test<Same<Typeof<keyof any>, 'string' | 'number' | 'symbol'>>
| Test<Same<Typeof<Map<string, number>>, 'object'>>
| Test<Same<Typeof<never | null>, 'object'>>
| Test<Same<Typeof<never | object>, 'object' | 'function'>>
| Test<Same<Typeof<never | string>, 'string'>>
| Test<Same<Typeof<never>, never>>
| Test<Same<Typeof<new () => {}>, 'function'>>
| Test<Same<Typeof<NonNullable<unknown>>, TypeofStringDefined>>
| Test<Same<Typeof<null>, 'object'>>
| Test<Same<Typeof<number>, 'number'>>
| Test<Same<Typeof<Number>, 'number' | 'object'>>
| Test<Same<Typeof<object>, 'object' | 'function'>> // that's not confusing at all.
| Test<Same<Typeof<object & (() => void)>, 'function'>>
| Test<Same<Typeof<object & { a: 1 }>, 'object'>>
| Test<Same<Typeof<object | string>, 'object' | 'function' | 'string'>>
| Test<Same<Typeof<object | undefined>, 'object' | 'function' | 'undefined'>>
| Test<Same<Typeof<object>, 'object' | 'function'>>
| Test<Same<Typeof<Omit<{ a: 1; b: 2; c: 3 }, 'b'>>, 'object'>>
| Test<Same<Typeof<Parameters<(x: number, y: string) => void>>, 'object'>>
| Test<Same<Typeof<Partial<{ a: 1; b: 2 }>>, TypeofStringDefined>>
| Test<Same<Typeof<Pick<{ a: 1; b: 2; c: 3 }, 'a' | 'c'>>, 'object'>>
| Test<Same<Typeof<Promise<string>>, 'object'>>
| Test<Same<Typeof<readonly [1, 2, 3]>, 'object'>>
| Test<Same<Typeof<Record<'a' | 'b', number>>, 'object'>>
| Test<Same<Typeof<Record<PropertyKey, never>>, 'object'>>
| Test<Same<Typeof<Record<string, never> | string>, 'object' | 'string'>>
| Test<Same<Typeof<Record<string, never>>, 'object'>>
| Test<Same<Typeof<Record<string, string | number>>, 'object'>>
| Test<Same<Typeof<RegExp>, 'object'>>
| Test<Same<Typeof<Required<{ a?: 1; b?: 2 }>>, 'object'>>
| Test<Same<Typeof<ReturnType<() => { a: 1 }>>, 'object'>>
| Test<Same<Typeof<ReturnType<() => string>>, 'string'>>
| Test<Same<Typeof<Set<number>>, 'object'>>
| Test<Same<Typeof<string & { __brand: 'x' }>, 'string'|'object'>>
| Test<Same<Typeof<string | {}>, TypeofStringDefined>>
| Test<Same<Typeof<string | number | boolean>, 'string' | 'number' | 'boolean'>>
| Test<Same<Typeof<String>, 'string' | 'object'>>
| Test<Same<Typeof<string>, 'string'>>
| Test<Same<Typeof<Symbol>, 'symbol'|'object'>>
| Test<Same<Typeof<symbol>, 'symbol'>>
| Test<Same<Typeof<true | false>, 'boolean'>>
| Test<Same<Typeof<typeof BigInt>, 'function'>>
| Test<Same<Typeof<typeof Boolean>, 'function'>>
| Test<Same<Typeof<typeof Date>, 'function'>>
| Test<Same<Typeof<typeof Error>, 'function'>>
| Test<Same<Typeof<typeof HTMLElement | null>, 'object' | 'function'>>
| Test<Same<Typeof<typeof Number | number>, 'function' | 'number'>>
| Test<Same<Typeof<typeof Number>, 'function'>>
| Test<Same<Typeof<typeof RegExp>, 'function'>>
| Test<Same<Typeof<typeof String | string>, 'function' | 'string'>>
| Test<Same<Typeof<typeof Symbol>, 'function'>>
| Test<Same<Typeof<undefined>, 'undefined'>>
| Test<Same<Typeof<unknown & {}>, TypeofStringDefined>>
| Test<Same<Typeof<unknown>, TypeofString>>
| Test<Same<Typeof<void>, TypeofString>>
| Test<Same<Typeof<Object>, TypeofStringDefined>>
| Test<Same<Typeof<{ length: number }>, 'string' | 'object' | 'function'>>
| Test<Same<Typeof<{ toString(): string }>, TypeofStringDefined>>
| Test<Same<Typeof<{ valueOf(): unknown }>, TypeofStringDefined>>
| Test<Same<Typeof<{ valueOf(): string }>, 'object' | 'string'>>
| Test<Same<Typeof<{ valueOf(): String }>, 'object' | 'string'>>
| Test<Same<Typeof<{ valueOf(): symbol }>, 'object' | 'symbol'>>
| Test<Same<Typeof<{ valueOf(): Symbol }>, 'object' | 'symbol'>>
| Test<Same<Typeof<{ valueOf(): boolean }>, 'object' | 'boolean'>>
| Test<Same<Typeof<{ valueOf(): Boolean }>, 'object' | 'boolean'>>
| Test<Same<Typeof<{ valueOf(): number }>, 'object' | 'number'>>
| Test<Same<Typeof<{ valueOf(): Number }>, 'object' | 'number'>>
| Test<Same<Typeof<{ valueOf(): bigint }>, 'object' | 'bigint'>>
| Test<Same<Typeof<{ valueOf(): BigInt }>, 'object' | 'bigint'>>
| Test<Same<Typeof<{ valueOf(): bigint | number }>, 'bigint' | 'number' | 'object'>>
| Test<Same<Typeof<{ valueOf(): object }>, 'object' | 'function'>>
| Test<Same<Typeof<{ (): void; name: unknown }>, 'function'>>
| Test<Same<Typeof<{ (): void }>, 'function'>>
| Test<Same<Typeof<{ x?: number, toString(): string }>, TypeofStringDefined>>
| Test<Same<Typeof<Typeof<unknown>>, 'string'>>
; // prettier-ignore
type Test<T extends true> = T;
type Same<Actual, Expected> = [Actual] extends [Expected] ? ([Expected] extends [Actual] ? true : false) : false;
/* type k0 = unknown;
type k1 = {}; // all
type k2 = object; // object|function
type k3 = Record<string, never>; // object
type k4 = { [k: string]: never }; // object
type k5 = (...args: any[]) => any;
type k6 = Function;
type k7 = never;
type k8 = { [K in never]: never };
type k9 = Object;
type k10 = void; */
/*
## Types
| # | type | keyof | expected typeof |
| --- | ------------------------- | ------------------ | ------------------------ |
| 0 | `unknown` | `never` | `TypeofReturn` |
| 1 | `{}` | `never` | `TypeofReturnDefined` |
| 9 | `Object` | (large key union) | `TypeofReturnDefined` |
| 2 | `object` | `never` | `'object' \| 'function'` |
| 5 | `(...args: any[]) => any` | `never` | `'function'` |
| 6 | `Function` | (large key union) | `'function'` |
| 3 | `Record<string, never>` | `string` | `'object'` |
| 4 | `{ [k: string]: never }` | `string \| number` | `'object'` |
| 8 | `{ [K in never]: never }` | `never` | `TypeofReturnDefined` |
| 7 | `never` | `never` | `never` |
| 10 | `void` | `never` | `TypeofReturn` |
## Extends (row extends column)
| extends ↓ / → | unknown (0) | void (9) | {} (1) | Object (9) | object (2) | fn (5) | Function (6) | Rec (3) | Idx (4) | {}₀ (8) | never (7) | void
| ------------- | ----------- | -------- | ------ | ---------- | ---------- | ------ | ------------ | ------- | ------- | ------- | --------- |
| unknown (0) | x | x | | | | | | | | | |
| void (10) | x | x | | | | | | | | | |
| {} (1) | x | | x | x | | | | | | x | |
| Object (9) | x | | x | x | x | | | | | x | |
| object (2) | x | | x | x | x | | | | | x | |
| fn (5) | x | | x | x | x | x | x | | | x | |
| Function (6) | x | | x | x | x | | x | | | x | |
| Rec (3) | x | | x | x | x | | | x | x | x | |
| Idx (4) | x | | x | x | x | | | x | x | x | |
| {}₀ (8) | x | | x | x | x | | | | | x | |
| never (7) | x | x | x | x | x | x | x | x | x | x | x |
### Legend
- `fn (5)` = `(...args: any[]) => any`
- `Rec (3)` = `Record<string, never>`
- `Idx (4)` = `{ [k: string]: never }`
- `{ }₀ (8)` = `{ [K in never]: never }` (alias of `{}`)
*/
export type Typeof<T> = void extends T // explicitly treat void as a top type to account for lambda returns
? 'object' | 'function' | 'string' | 'number' | 'bigint' | 'boolean' | 'symbol' | 'undefined'
: TypeofType<T, undefined, 'undefined'> | TypeofType<T, null, 'object'> | TypeofImpl<T & {}>;
type TypeofImpl<T> = T extends ((...args: never) => unknown) | (abstract new (...args: never) => unknown)
? 'function'
:
| TypeofType<T, string, 'string'>
| TypeofType<T, boolean, 'boolean'>
| TypeofType<T, number, 'number'>
| TypeofType<T, symbol, 'symbol'>
| TypeofType<T, bigint, 'bigint'>
| TypeofType<T, ((...args: never) => unknown) | (abstract new (...args: never) => unknown), 'function'>
| TypeofType<T, object, 'object'>;
type TypeofType<A, B, S> = A extends B ? S : B extends A ? S : B extends OmitOptional<A> ? S : never;
type OmitOptional<T> = T extends object
? { [P in keyof T as Pick<T, P> extends Required<Pick<T, P>> ? P : never]: T[P] }
: T;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment