---
title: "User-defined functions"
description: "Create custom, reusable functions with the function keyword: typed parameters, a typed return, default values, functions as values, and the rules that keep…"
order: 28
section: "core-concepts"
---

<!-- source: docs/indicators/core-concepts/user-functions.md; generated by packages/cli/scripts/gen-indicator-docs.ts, do not edit -->

# User-defined functions

Create custom, reusable functions with the `function` keyword: typed
parameters, a typed return, default values, functions as values, and the
rules that keep them honest in a per-bar module. kScript (legacy) v3
introduced `func` with inferred types and keyword-argument calls; an
Indicator's functions are plain TypeScript, with every type written out and
module-level state in place of closures.

## Overview

| Feature | Description |
| --- | --- |
| `function` | The keyword; declares a named function with typed parameters and return |
| Any logic | Encapsulate a calculation, a predicate, a candle pattern, a session test |
| Module state | Functions read and write module-level variables; that is how state reaches them |
| Values | A non-capturing function is a value you can store and pass |

## Function declaration

### Basic syntax

```text
function functionName(parameter1: f64, parameter2: f64): f64 {
  // body
  return result;
}
```

Every parameter carries a type and the return type is written after the
parameter list. Leaving the return type off is a parse error (`Type
expected.`), so annotate even `void`.

### Calling

Calls are positional, in the declared order. There are no keyword
arguments; readable names and a stable order do the same job, and a
function with many settings takes a class instance instead of a long
argument list. Parameters may carry defaults:

```text
function calculate(base: f64, multiplier: f64, offset: f64 = 0.0): f64 {
  return base * multiplier + offset;
}

const a = calculate(10.0, 2.0, 5.0);   // 25
const b = calculate(10.0, 2.0);        // 20, offset defaulted
```

### Syntax details

**Function name:** standard identifier rules (letters, digits,
underscore; not starting with a digit). The four names `init`, `state`,
`finalize`, and `reset` are the module's exports and the host's contract;
everything else is yours and needs no `export`.

**Parameters:** typed, positional, optionally defaulted. A parameter is an
`f64` for a price or a value, an `i32` for a count, a `StaticArray<f64>`
for a window, a class for a struct, or a function type for a callback.

**Return statement:** every path returns a value of the declared type
(`void` returns nothing). The compiler refuses a missing return.

## Function examples

### Safe division

```text
function safeDiv(a: f64, b: f64): f64 {
  return b == 0.0 ? 0.0 : a / b;
}

const ratio = safeDiv(10.0, 2.0);   // 5
const safe = safeDiv(10.0, 0.0);    // 0
```

### Average of two values

```text
function average(a: f64, b: f64): f64 {
  return (a + b) / 2.0;
}

const mid = average(close, prevClose);
```

### Custom pattern logic

A candle-pattern test takes this bar's and the previous bar's open and
close. Where kScript indexed `open[1]`, an Indicator remembers the previous
bar's values in module-level variables at the end of `state()`:

```typescript
import { input, line, lower, none, ohlcv, output, overlay, shape } from "./sdk/declare";
import { in_close, in_low, in_open } from "./gen/inputs";
import { emitRow, out_body_ratio, out_engulfing, out_is_engulfing } from "./gen/outputs";

input("open", ohlcv.open);
input("close", ohlcv.close);
input("low", ohlcv.low);
output("engulfing", shape, overlay, { color: "#16a34a", shape_where: "is_engulfing", description: "Bullish engulfing candle" });
output("is_engulfing", none);
output("body_ratio", line, lower, { color: "#94a3b8", description: "This body divided by the previous body" });

function safeDiv(a: f64, b: f64): f64 {
  return b == 0.0 ? 0.0 : a / b;
}

function isGreenCandle(openPrice: f64, closePrice: f64): bool {
  return closePrice > openPrice;
}

function isBullishEngulfing(prevOpen: f64, prevClose: f64, currOpen: f64, currClose: f64): bool {
  const prevWasRed = prevClose < prevOpen;
  const currIsGreen = isGreenCandle(currOpen, currClose);
  const engulfs = currOpen < prevClose && currClose > prevOpen;
  return prevWasRed && currIsGreen && engulfs;
}

let prevOpen: f64 = NaN;
let prevClose: f64 = NaN;
let low: f64 = NaN;
let engulfing: bool = false;
let bodyRatio: f64 = NaN;

export function init(): void {}

export function state(): i32 {
  const open = in_open();
  const close = in_close();
  low = in_low();
  const ready = !isNaN(prevOpen);
  engulfing = ready && isBullishEngulfing(prevOpen, prevClose, open, close);
  bodyRatio = ready ? safeDiv(Math.abs(close - open), Math.abs(prevClose - prevOpen)) : NaN;
  prevOpen = open;
  prevClose = close;
  return ready ? 1 : 0;
}

export function finalize(): void {
  out_engulfing(low); // the mark sits under the candle
  out_is_engulfing(engulfing ? 1.0 : 0.0);
  out_body_ratio(bodyRatio);
  emitRow();
}

export function reset(): void {
  prevOpen = NaN;
  prevClose = NaN;
  low = NaN;
  engulfing = false;
  bodyRatio = NaN;
}
```

The three helpers are pure: they take numbers and return a number or a
`bool`, and `state()` supplies the remembered previous bar. That keeps the
pattern testable in isolation and the per-bar function short.

## Functions as values

A function that captures no local variable is a value. Store it in a
variable typed with its signature, pass it to a loop, keep a small table of
them:

```text
function aboveMean(x: f64): bool { return x > mean; }   // mean is module-level

let pred: (x: f64) => bool = aboveMean;
const hits = countWhere(window, n, pred);
```

Arrow functions work the same way, and nested functions (declared inside
another function) are allowed as long as they read nothing from the
enclosing function's locals. The full treatment, with the loop that takes
a predicate, is `lambdas-and-reducers.md`.

## Constraints and rules

### No declarations inside functions

`param(...)`, `input(...)`, `output(...)`, `box(...)`, and the rest are
top-level statements of the file; the build reads them without running the
code. One inside a function is a named build error:

```text
output(...) declarations must be top-level statements, not inside a function, class, or expression
```

Pass an input's value in as an argument instead:

```text
// Invalid
function bad(): f64 {
  input("close", ohlcv.close);   // a declaration cannot live here
  return 0.0;
}

// Valid: declare at the top level, pass the value
function good(close: f64, avg: f64): f64 {
  return close - avg;
}
```

### No locals from the outside

A function reads its parameters and module-level state. An inner function
or arrow that reads a local of the function around it is refused with
`AS100: Not implemented: Closures`. Move the value to module scope or pass
it as a parameter.

### Allocation is once, not per call

A function called in `state()` runs on every bar. A `new` inside it (a
class, an array, a string built with `+`) allocates memory the module never
frees. Helpers on the per-bar path take and return numbers, or write into
buffers allocated at module scope.

### Types everywhere

Parameter types and the return type are required; there is no inference
from usage. A function meant for both a price and a count is written for
`f64`, and the caller casts (`f64(count)`).

### Phase rules still apply inside functions

`p_<param>()` is legal in `init()`, `in_<input>()` in `state()`,
`out_<output>()` and `emitRow()` in `finalize()`; a helper inherits the
phase of its caller, so keep helpers pure and let the four exports read and
write.

## Best practices

- **Keep functions focused.** One thing per function: `rsiZone(rsiValue:
  f64): f64` returns `0`, `1`, or `2`; the output write stays in
  `finalize()`.
- **Use descriptive names.** `isOverbought(rsiValue)`,
  `percentChange(oldValue, newValue)`; not `calc(a, b)` or `check(x)`.
- **Prefer parameters to module state for pure math.** A function that
  takes everything it needs is reusable across Indicators by pasting; a
  function that reaches into module state is tied to this file. Reserve
  module-state reads for predicates you pass as values.
- **Turn a family of helpers into a class** when they share state: a
  `Window` with `sum()`, `avg()`, and `median()` (`collections.md`), a
  `Macd` with `update()` (`named-streams.md`). Helpers that share nothing
  stay functions.
