Skip to main content
numpy-ts is written in TypeScript and exports types for arrays, dtypes, and all function signatures. This guide covers patterns for writing type-safe numerical code.

Importing types

Use import type for type-only imports to ensure they are erased at compile time:
Or from the core entry point:

The DType type

DType is a string union representing all supported data types:
Use it to type dtype parameters in your functions:

NDArray vs NDArrayCore

The two array types correspond to the two main entry points: NDArray extends NDArrayCore, so every NDArray satisfies the NDArrayCore type. Both types expose the same properties: shape, ndim, size, dtype, data, strides, flags, base, itemsize, nbytes.

Writing generic functions

Accept NDArrayCore for maximum compatibility

If your function only uses standalone functions (not method chaining), accept NDArrayCore. This lets callers pass either type:

Accept NDArray when you need methods

If your function uses method chaining, require NDArray:

Pattern for library authors

If you are publishing a library that depends on numpy-ts, accept NDArrayCore in your public API and return NDArrayCore. This gives your users the choice of which entry point to use.
By depending on numpy-ts/core, your library only pulls in the functions it uses. If a consumer imports your library alongside numpy-ts, there is no duplication — the same underlying computation code is shared.

Type narrowing with dtype

The dtype property is typed as the array’s dtype parameter D (which is DType for an unparameterized NDArray). When you branch on the dtype of an unparameterized array, switch narrows it directly:
You can also use the utility functions isIntegerDType, isFloatDType, and isComplexDType:

Shape inference

Array shapes are exposed as readonly number[]. You can use this to write shape-aware utilities:

Combining with generics

For functions that work with multiple array types, use TypeScript generics:
Since NDArray extends NDArrayCore, a generic bounded by NDArrayCore accepts both types. The return type preserves the caller’s type through the generic parameter.

Type-safe creation functions

Creation functions take the dtype as a positional argument, and the dtype literal flows into the return type (see Compile-time dtype tracking below):
When creating arrays from data, the dtype is inferred at runtime from the values or set explicitly (an explicit literal is tracked in the type):

Compile-time dtype tracking

NDArray<D> is parameterized by its dtype. When you create an array with a dtype literal, that literal is carried through the type system so operations report their exact result dtype — following the same NumPy promotion rules enforced at runtime.

Element access returns the right scalar type

get, iget, and item return bigint for int64/uint64, a Complex for complex dtypes, and number otherwise.

Result dtypes are tracked per operation

Each operation reports the dtype NumPy would produce:
The same rules apply to the free-function API, and dtype is inferred from the operands:
Every operation with a static result-dtype rule reports its dtype — shape ops, reductions, contractions, binary ufuncs, complex components, tuple-returning ufuncs, and index families:

The promotion type helpers

The promotion rules are exposed as type-level helpers you can use directly, generated from the same single source of truth as the runtime rules:

Caveats

  • Scalar operands keep the array’s dtype. intArr.add(2.5) is typed NDArray<'int32'>, not float. TypeScript sees only the type number, never the value, so it cannot distinguish an integer literal from a float — modelling NumPy’s weak-scalar promotion is not possible. Use an explicit astype when you need the widened result.
  • Runtime-computed dtypes widen to DType. When a dtype is chosen at runtime (array(data) without a literal, or a DType-typed variable), the result is NDArray<DType> — correct, just not narrowed.
  • A few operations stay NDArray<DType> on purpose — their result dtype can’t be known from the types. This is honest widening, not a gap: multi-array joins (concatenate, stack, hstack, …) and choose/select, whose NDArray[] inputs have already erased the per-array dtype; data/file/callback sources (array(data), fromfile, apply_along_axis); and a few data-dependent functions (poly, roots, whose output is real or complex depending on the values).
  • Index results diverge from NumPy. NumPy returns int64/intp for index-producing ops; numpy-ts returns number-yielding dtypes instead, so element access stays number rather than bigint (int64 is BigInt-backed here). Concretely: argmin/argmax/nanargmin/nanargmaxint32, and argsort/argpartition/argwhere/flatnonzero/nonzerofloat64. Both the types and the runtime agree on this. Caveat: the int32 arg-reductions overflow above ~2.1 billion (2³¹) elements along the reduced axis; the float64 family is safe to 2⁵³. A unified 53-bit index dtype (intp) is planned to remove this split — see the roadmap.
  • float16 is float32-backed without native support. The type↔runtime dtype match holds everywhere except float16 on engines lacking a native Float16Array (older runtimes). There, a float16 result is stored in a Float32Array, so its runtime .dtype reads 'float32' while the compile-time type stays 'float16'. Scalar/element types are unaffected.