Back to Functions

Functions

Defining Functions

Skip to 3 practice questions

Practice questions on writing functions in q — implicit and explicit arguments, local and global scope, brace conventions, and recursion.


What Is a Function?

A function is a reusable block of logic that takes some inputs, does something with them, and gives back a result. In q, every function is written inside curly braces {}. Because functions are ordinary values in q, you can assign them to names, pass them to other functions, or put them in a list.

/ Define and name a function
square: {x * x}
square[5]     / 25

/ Call it without giving it a name
{x * x}[5]   / 25 — same result, no name needed

The function hands back whatever its last expression evaluates to. Any earlier expressions are run for their side effects (creating variables, sending messages, etc.) and their values are thrown away.


Choosing How to Name Arguments

The short way: x, y, z

Q sets aside three letters — x, y, and z — as ready-made argument names. If you write a function that uses them without declaring a parameter list, q works out the rank (how many arguments the function takes) automatically:

{x + 1}         / one argument  — just x
{x + y}         / two arguments — x then y
{x + y + z}     / three arguments

This saves typing for short, obvious functions. The trade-off is that a reader has to infer what x, y, and z represent from context.

Keep these letters for their intended purpose — don't use them as ordinary variable names inside a function body. Doing so makes it impossible to tell whether a reference is to the argument or to some local variable you created later.

The clear way: named parameters

Place your own names between square brackets at the start of the function, separated by semicolons. Q supports up to eight of them:

midpoint:{[bid; ask]
  (bid + ask) % 2
 }
midpoint[100.5; 101.0]
/ 100.75

vwap:{[price; qty]
  sum[price * qty] % sum qty
 }
vwap[100 101 102; 200 300 100]
/ 100.833...

Named parameters pay for themselves as soon as a function does more than one thing, or whenever the argument names carry domain meaning (like bid, ask, price, qty) that x, y, z cannot convey.


Multiple Expressions Inside a Function

Semicolons separate expressions inside a function body. Q runs them left to right, and the function returns the value of the last one:

f:{
  a: x * 2;     / runs first  — result stored in a, not returned
  b: a + 1;     / runs second — result stored in b, not returned
  b * b          / runs last   — this is what the caller gets back
 }

f[3]  / 49   (3×2=6, 6+1=7, 7×7=49)

Variables: Staying Inside vs Reaching Outside

When you write name: value inside a function, that name belongs only to that one call. It springs into existence when the function starts and disappears when it finishes. Code outside the function cannot see it, and calling the function again starts with a blank slate:

f:{a: x + 1; a * a}
f[4]   / 25
a      / error — `a` doesn't exist here

Sometimes you genuinely need to update something that lives outside the function — a running total, a shared counter, a cache. For that, use double colon :::

total: 0
accumulate:{total +:: x}

accumulate each 1 2 3 4 5
total  / 15

+:: reads as "add to the global in place". Alternatively, `total set total + x does the same job using the call-by-name form, which some teams find more explicit.

Lean towards keeping side effects out of functions wherever possible. Functions that only use their arguments and return a value are much easier to test and to reason about.


Laying Out Function Code

Short functions — one line

When a function does one thing, write it on a single line:

square:  {x * x}
negate:  {neg x}
average: {sum[x] % count x}

Longer functions — spread across lines

Indent the body (in a script, every line after the first must be indented). A common convention is to put the closing brace on the same line as the last expression when the function returns a value:

vwap:{[price; qty]
  notional: sum price * qty;
  volume:   sum qty;
  notional % volume}

Functions that produce side effects only

Some functions exist only to do something — publish a message, write to a global, log an event — and return nothing meaningful. End the last expression with a semicolon, so the function returns the generic null ::, and by convention put the closing brace on a line of its own as a visual marker that the caller should not expect a useful return value:

logTrade:{[sym; qty; price]
  msg: "TRADE: ", string[sym], " ", string[qty], "@", string price;
  -1 msg;
  }

Calling Functions

add:{x + y}
sq:{x * x}

add[10; 20]  / 30 — bracket notation: works for any number of arguments
sq 10        / 100 — prefix notation (juxtaposition): one argument only

Unlike built-in operators such as +, a function you define yourself cannot be called infix: 10 add 20 is an error. Bracket notation is always unambiguous and is the safest default, especially in code others will read.


Recursion

A named function can refer to itself by name in its own body. Always check the stopping condition first, or the function will call itself forever:

factorial:{$[x <= 1; 1; x * factorial[x-1]]}
factorial[6]   / 720

For anonymous functions that have no name to refer back to, q provides .z.s — a built-in that always points to the function currently running:

{$[x <= 1; 1; x * .z.s[x-1]]}[6]   / 720

In practice, q programmers reach for iterators rather than recursion for most accumulation tasks, since iterators are shorter and avoid the overhead of repeated function calls:

(*/) 1 + til 6   / 720 — multiply 1 through 6 together using over

Save recursion for genuinely recursive structures — trees, nested lists, anything where the depth is not known in advance.

Practice Questions

Test your understanding with 3 hands-on questions