How to work with atoms¶
This page introduces the atom types in q and highlights the behavior that is specific to q.
An atom is an irreducible value of a specific data type. The basic data types in q correspond mostly to those of traditional programming languages, with additional date- and time-related types that facilitate time series.
This is not a comprehensive guide to atoms. It focuses on the features specific to q. Refer to Q for Mortals §2. Basic Data Types – Atoms for a comprehensive guide and to the datatypes reference card for a quick reference page.
Every atom has a negative type number. The corresponding list type is the same number, positive:
q)type 42 / long atom
-7h
q)type 42 43 / long list
7h
The following table summarizes the atom types. Each has a literal form, and most have a dedicated null and infinity literal:
| Type | Size (bytes) | Example literal | Null | Infinity |
|---|---|---|---|---|
| boolean | 1 | 1b |
||
| guid | 16 | 0Ng |
||
| byte | 1 | 0x2a |
||
| short | 2 | 42h |
0Nh |
0Wh |
| int | 4 | 42i |
0Ni |
0Wi |
| long | 8 | 42 |
0N |
0W |
| real | 4 | 3.14e |
0Ne |
0We |
| float | 8 | 3.14 |
0n |
0w |
| char | 1 | "a" |
" " |
|
| symbol | `abc |
` |
||
| timestamp | 8 | 2026.09.10D14:30:00.000000000 |
0Np |
0Wp |
| month | 4 | 2026.09m |
0Nm |
0Wm |
| date | 4 | 2026.09.10 |
0Nd |
0Wd |
| timespan | 8 | 0D14:30:00.000000000 |
0Nn |
0Wn |
| minute | 4 | 14:30 |
0Nu |
0Wu |
| second | 4 | 14:30:00 |
0Nv |
0Wv |
| time | 4 | 14:30:00.000 |
0Nt |
0Wt |
Boolean¶
Booleans are one byte each and are written with a b suffix. A boolean list is written as a run of digits followed by b:
q)type 1b
-1h
q)0101b / boolean list, no separators needed
0101b
Booleans behave as 0 and 1 in arithmetic. Note that they are promoted to int, not long:
q
q)1b + 1b
2i
q)sum 1011b
3i
Python
>>> True + True
2
>>> sum([True, False, True, True])
3
Logical and and or are the & (lesser) and | (greater) operators, which is why they generalize to non-boolean types. The keywords and and or are synonyms. Use any and all to aggregate:
q)1b & 0b
0b
q)0b | 1b
1b
q)not 1b
0b
q)(any; all) @\: 0011b
10b
Casting to boolean maps zero to 0b and everything else to 1b:
q)`boolean$0 1 2
011b
q)"b"$1
1b
Byte¶
A byte is written with the 0x prefix. Bytes are the raw building block of serialization and bit manipulation:
q)type 0x2a
-4h
q)"x"$42
0x2a
Use vs to split a value into its byte or bit representation:
q)0x0 vs 42i / byte representation of an int
0x0000002a
q)0b vs 0x2a / bit representation of a byte
00101010b
Byte has no null and no infinity
Every byte pattern is a valid value, so there is nothing left over to reserve for a null. Consequently null never returns 1b for a byte, and byte columns cannot represent "missing".
q)null 0x00
0b
Integers (short, int, long)¶
q has three signed integer widths: short (2 bytes), int (4 bytes), and long (8 bytes). long is the default, so an integer literal without a suffix is a long:
q)type 42
-7h
q)type 42i
-6h
q)type 42h
-5h
There are no unsigned integers in q
If you need to interpret unsigned data, either read it as a byte vector or widen it to the next larger signed type. There is also no dedicated 128-bit type.
Arithmetic on short and int is promoted to int; only long arithmetic stays long:
q)type 42h + 42h
-6h
q)type 42i + 42i
-6h
There is no overflow detection. Exceeding the range of a type silently wraps, which can land exactly on the null value:
q)2147483647i + 1i
0Ni
Division with % always returns a float, regardless of the operand types. For integer division and remainder use div and mod, which round toward negative infinity:
q
q)type 7 % 2 / always float
-9h
q)7 div 2
3
q)7 mod 2
1
q)-7 div 2 / floor, not truncation
-4
q)-7 mod 2
1
Python
>>> type(7 / 2)
<class 'float'>
>>> 7 // 2
3
>>> 7 % 2
1
>>> -7 // 2
-4
>>> -7 % 2
1
Floating point (real, float)¶
float (8 bytes) is the default; real (4 bytes) is written with an e suffix:
q)type 3.14
-9h
q)type 3.14e
-8h
It is useful to understand the precision of a floating-point number. By default, the console shows seven significant digits, which hides the stored value. Use the \P system command to see everything:
q)1 % 3
0.3333333
q)\P 0
q)1 % 3
0.33333333333333331
q)0.1 + 0.2
0.30000000000000004
q)\P 7 / restore the default display precision
Comparing floating-point numbers might surprise you. The = operator implements tolerant equality: two floats compare equal if their difference is small relative to their magnitude. Test for exact equality with 0 = x - y:
q)(0.1 + 0.2) = 0.3 / tolerant equality
1b
q)0 = 0.3 - (0.1 + 0.2) / exact equality
0b
More about float precision.
Formatting a number for display¶
\P changes the console display globally. To format an individual value instead, two utilities return the number as a string:
q).Q.f[2; 3.14159] / 2 decimal places
"3.14"
q).Q.fmt[8; 2; 3.14159] / 2 decimal places, padded to a width of 8
" 3.14"
.Q.fmt pads to a fixed width, which is what you want for aligning a column of numbers.
Text data: characters, strings, and symbols¶
q has two text types, and the distinction matters:
char("a") is an atom of one character. A string is a list of chars — there is no string atom.symbol(`abc) is the atomic text type: however long the name, a symbol is a single, indivisible value.
q)type "a" / char atom
-10h
q)type "abc" / char list, that is, a string
10h
q)type `abc / symbol atom
-11h
q)count "abc"
3
q)count `abc / a symbol is an atom
1
Symbols are interned: each distinct symbol is stored once in a global pool and referenced by an index. This makes comparison very fast and storage constant per value, so symbols are the natural choice for repeated, low-cardinality values such as instrument names. They are also the basis of enumerations. The cost is that the pool never shrinks during a session, so avoid creating symbols out of unbounded, high-cardinality data.
A symbol never matches a string, and comparing the two raises an error:
q)`abc ~ "abc"
0b
q)`abc = "abc"
'type
[0] `abc = "abc"
^
Convert between the two with string and $. Use `$ for symbols whose names cannot otherwise be written:
q)string `abc
"abc"
q)`$"abc"
`abc
q)`$"hello world" / symbols can contain spaces
`hello world
Pattern matching with like¶
Use like to match text against a pattern. It accepts * (any sequence), ? (any single character), and [] (character class), and it iterates over a symbol list:
q)"Alice" like "A*"
1b
q)"Alice" like "?lice"
1b
q)"Alice" like "[AB]lice"
1b
q)"Alice" like "a*" / case sensitive
0b
q)`Alice`Bob`Charlie like "*e" / iterates over a symbol list
101b
For substring search and replacement, use ss and ssr, and for case conversion lower and upper:
q
q)ssr["hello world"; "world"; "q"]
"hello q"
q)upper "alice"
"ALICE"
q)lower `ALICE / also applies to symbols
`alice
Python
>>> "hello world".replace("world", "q")
'hello q'
>>> "alice".upper()
'ALICE'
Character constants¶
The .Q namespace provides ready-made character vectors that save you typing character ranges:
q).Q.A / upper-case alphabet
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
q).Q.a / lower-case alphabet
"abcdefghijklmnopqrstuvwxyz"
q).Q.an / all alphanumerics, including underscore
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_0123456789"
Temporal values¶
Rich temporal support is what makes q a time-series language. The primary types are timestamp, date, timespan, and time, with nanosecond precision available where it matters:
q)type 2026.09.10 / date
-14h
q)type 2026.09.10D14:30:00.123456789 / timestamp, nanosecond precision
-12h
q)type 0D14:30:00.000000000 / timespan, nanosecond precision
-16h
q)type 14:30:00.000 / time, millisecond precision
-19h
Also available are coarser types, useful as bucketing keys: month, minute, and second:
q)type 2026.09m / month
-13h
q)type 14:30 / minute
-17h
q)type 14:30:00 / second
-18h
Temporal values are encoded as integers counting from the epoch 2000.01.01, so arithmetic works as you would expect and casting to an integer exposes the underlying count:
q
q)2026.09.10 + 1 / next day
2026.09.11
q)2026.09.10 - 2026.09.01 / difference in days
9i
q)"j"$2026.09.10 / days since the epoch
9749
q)"j"$2026.09.10D00:00:00.000000001 / one nanosecond past midnight
842313600000000001
q)2026.09.10D14:30:00 + 0D01:00:00 / add a duration
2026.09.10D15:30:00.000000000
Python
>>> from datetime import date, datetime, timedelta
>>> date(2026, 9, 10) + timedelta(days=1)
datetime.date(2026, 9, 11)
>>> date(2026, 9, 10) - date(2026, 9, 1)
datetime.timedelta(days=9)
>>> (date(2026, 9, 10) - date(2000, 1, 1)).days
9749
>>> datetime(2026, 9, 10) + timedelta(microseconds=0.001)
datetime.datetime(2026, 9, 10, 0, 0)
>>> datetime(2026, 9, 10, 14, 30) + timedelta(hours=1)
datetime.datetime(2026, 9, 10, 15, 30)
Two differences are worth dwelling on. Temporal arithmetic in q needs no wrapper type: a date plus an integer is a date, and a difference is a plain number you can aggregate. And the nanosecond example is not a like-for-like comparison — q addresses a single nanosecond directly, whereas the standard library's datetime resolves only to microseconds and discards the nanosecond without complaint.
Adding a date and a time yields a timestamp, and adding a date and a timespan does the same — which is what the D in a timestamp literal denotes:
q)2026.09.10 + 14:30:00.000 / date + time
2026.09.10D14:30:00.000000000
q)2026.09.10 + 0D14:30:00 / date + timespan
2026.09.10D14:30:00.000000000
Comparing different temporal types
When you compare two different temporal types, one side is converted, and the direction of that conversion is not always the one you expect. Comparing a timestamp with a minute truncates the timestamp to a minute, which silently discards seconds. See comparison of temporal values for the full conversion matrix.
Dot notation and casting¶
Dot notation extracts a component of a temporal value. The type names (date, time, minute, second, month) return a temporal value of that type; the component names (year, mm, dd, hh, uu, ss) return an int:
q)d: 2026.09.10D14:35:27.123456789
q)d.date
2026.09.10
q)d.minute
14:35
q)d.month
2026.09m
q)d.year
2026i
q)d.mm
9i
q)d.hh
14i
Casting with $ is equivalent for the type-valued components, and is the form to prefer in code because it also works on lists and inside queries:
q)`date$d
2026.09.10
q)`minute$d
14:35
q)`timespan$d / time of day as a duration
0D14:35:27.123456789
Timezones¶
A timezone is not stored with a temporal value. A timestamp is just a count of nanoseconds; how you interpret it is a convention you must maintain yourself. The usual practice is to store UTC and convert on display.
q).z.p / current UTC timestamp
2026.09.10D12:40:38.048740000
q).z.P / current local timestamp
2026.09.10D14:40:38.048795000
q).z.D / current local date
2026.09.10
Convert between the two with gtime and ltime. The offset applied is the one in effect on the machine, so the results below are from a host set to UTC+2:
q)gtime 2026.09.10D14:00:00.000000000 / local to UTC
2026.09.10D12:00:00.000000000
q)ltime 2026.09.10D14:00:00.000000000 / UTC to local
2026.09.10D16:00:00.000000000
Month arithmetic¶
Days are a fixed length; months are not. Month arithmetic therefore needs .Q.addmonths:
q
q).Q.addmonths[2026.09.10; -3]
2026.06.10
Python
>>> from datetime import date
>>> d = date(2026, 9, 10)
>>> m = d.month - 1 - 3 # zero-based month index
>>> date(d.year + m // 12, m % 12 + 1, d.day)
datetime.date(2026, 6, 10)
The standard library has no month unit, only days and smaller, so the year and month wrap has to be done by hand.
Be aware that .Q.addmonths does not clamp to the end of the month. If the day of month does not exist in the target month, the result rolls over into the following month:
q).Q.addmonths[2026.01.31; 1] / 2026.02.31 does not exist
2026.03.03
The datetime type (15h) is deprecated
Use timestamp (12h) instead. datetime stores the time of day as a float fraction of a day, so it cannot represent every millisecond exactly, and equality comparisons on it are unreliable.
GUID¶
A GUID (globally unique identifier) is a 16-byte value, typically used for transaction and record IDs. Its q type name is guid:
q)type 0Ng
-2h
q)1?0Ng / generate a random guid with Deal
,8c6b8b64-6815-6084-0a3e-178401251b68
q)"G"$"8c680a01-5a49-5aab-5a65-d4bfddb6a661" / parse from a string
8c680a01-5a49-5aab-5a65-d4bfddb6a661
q)0x0 sv 16?0xff / build from 16 bytes
5a495aab-5a65-d4bf-ddb6-a66158e53a44
Deal (?0Ng) generates globally unique values: it mixes .z.a, .z.i, and .z.p.
0Ng is the null guid:
q)0Ng
00000000-0000-0000-0000-000000000000
q)null 0Ng
1b
Guids have no literal form, and their only conversion is parsing from a string with "G"$, as shown above. The only scalar primitives defined on them are =, <, and >, much like symbols.
Prefer guid over a char vector for IDs
A guid takes 16 bytes against 40 for the equivalent 16-character string, and comparing two guids with = is a single 16-byte comparison rather than a walk over a character list. Use int, symbol, or guid for IDs — not strings.
Null values¶
Each type reserves one value to mean "missing". The literal is 0N followed by the type character; float null is 0n:
q)(0N; 0Nh; 0Ni; 0Ne; 0n; 0Ng; 0Np; 0Nd)
0N
0Nh
0Ni
0Ne
0n
00000000-0000-0000-0000-000000000000
0Np
0Nd
Text nulls are less obvious: the null char is a space and the null symbol is the empty symbol. As stated earlier, byte has no null.
Use the null keyword rather than comparing against a literal — it is atomic and works across types:
q)null (0N; 0n; `; " "; 0Ng; 0Nd)
111111b
Three behaviors are worth internalizing.
Nulls are contagious in arithmetic. Any arithmetic involving a null yields a null:
q)0N + 5
0N
q)0n + 5
0n
Aggregations ignore nulls, but count does not. This means avg is not sum % count:
q
q)l: 1 2 0N 4
q)sum l
7
q)avg l / 7 % 3, not 7 % 4
2.333333
q)count l
4
Python
>>> import statistics
>>> l = [1, 2, None, 4]
>>> sum(x for x in l if x is not None)
7
>>> statistics.mean(x for x in l if x is not None)
2.3333333333333335
>>> len(l)
4
The same holds for weighted aggregations, and there the consequence is subtle: wavg discards the weight paired with a null, so it is not (sum w * x) % sum w:
q)w: 1 2 3 4
q)w wavg 1 2 0N 4 / the weight 3 is dropped along with the null
3f
q)1 2 4 wavg 1 2 4 / identical result
3f
q)(sum w * 1 2 0N 4) % sum w / what the naive formula gives
2.1
med is the exception
med sorts its argument and takes the middle, and because nulls sort first they are treated as values rather than skipped. Filter them out yourself if that is not what you want.
q)med 1 2 0N 4
1.5
q)med {x where not null x} 1 2 0N 4
2f
Nulls sort first and compare below every value, including negative infinity. Nulls of different types compare equal to each other:
q)asc (5; 0N; 1)
`s#0N 1 5
q)0N < -0W
1b
q)0N = 0Nh
1b
To replace nulls, use ^ Fill and fills:
q
q)0 ^ 0N 5 0N / replace nulls with 0
0 5 0
q)fills 0N 5 0N 7 / forward-fill
0N 5 5 7
Python
>>> [0 if x is None else x for x in [None, 5, None]]
[0, 5, 0]
>>> import itertools
>>> ff = lambda a, b: a if b is None else b
>>> list(itertools.accumulate([None, 5, None, 7], ff))
[None, 5, 5, 7]
Note that a leading null has nothing to carry forward, so fills leaves it in place.
Enumerated symbols are the exception
null does not detect a null symbol behind an enumeration, because that value is used to indicate out of range. See null for the workaround.
Infinite values¶
Each numeric and temporal type also reserves an infinity, written 0W followed by the type character:
q)(0W; 0Wh; 0Wi; 0w; 0Wp; 0Wd)
0W
0Wh
0Wi
0w
0Wp
0Wd
Float infinities follow IEEE 754 and behave as you would expect:
q)1 % 0
0w
q)-1 % 0
-0w
q)0w + 5
0w
q)0w - 0w / indeterminate, so null
0n
q)0 % 0
0n
Integer infinities are not special values — they are the largest representable integer, and integer arithmetic does no infinity checks. So arithmetic on them is undefined and wraps around:
q)0W + 1 / wraps onto the null value
0N
q)0Wi + 5i
-2147483644i
Treat integer infinities as placeholders only
Use them as sentinels — for example as the open end of a range — but do not perform arithmetic on them. See Infinities for the details.
Next Steps¶
- For more detail on atoms, read Q for Mortals §2. Basic Data Types – Atoms, and §2.7 Nulls for nulls in particular.
- Keep the datatypes reference card at hand for type numbers, literals, nulls, and infinities.
- Understand the pitfalls of float precision and of comparison across types.
- Collect atoms of the same type into a list, the core data structure of q.
- Learn more about handling temporal data in q.