HSTACK(array1, [array2], …) places its arguments side by side, left to right in the order
given, and returns the combined array.
The result's width is the sum of the arguments' widths. Its height is the height of the
tallest argument — and every argument shorter than that is padded at the bottom. Microsoft
documents the pad value as #N/A, which is the same convention VSTACK uses and the same
default EXPAND uses: synthesized cells announce themselves rather than passing as zeros.
That padding rule is what makes HSTACK a stacking operation rather than a join. It does not
align rows by any key; it aligns them by position, starting at row 1. Two lists of different
lengths stacked horizontally give you a two-column array whose shorter column trails off into
#N/A, which is correct and is very often not what the author meant.
Arguments
Argument
Meaning
array1
The first array or range. Required.
array2, …
Further arrays, in left-to-right order. Optional, to the arity ceiling of 255 arguments.
Scalars are admissible: a scalar is a 1×1 array, so HSTACK(A1:A10, "total") produces a
two-column result whose second column is one value and nine #N/As.
The misunderstanding to head off: HSTACK takes several arguments, not a multi-area
reference.HSTACK((A1:A5,C1:C5)) passes one union-shaped argument, which is a different
thing from HSTACK(A1:A5, C1:C5). What the union form does is not established here.
Try it
Quirks
Result and edge cases
Return kind: Array, spilled. Spilling is host-side adaptation
(the call pipeline).
References are resolved first (ValuesOnlyPreAdapter), so the result carries values, not
reference structure. HSTACK cannot be used where a reference is required.
A single argument returns that argument as an array — the identity case, and a useful way
to force a reference into array form.
Element kinds are preserved: no coercion is applied to the stacked elements, and errors
inside the inputs pass through as values (error_collapse_profile: None).
Ragged inputs are the normal case, not an error. Padding is the documented behaviour;
there is no strict mode.
Whether a whole-column reference is admissible as an argument, and what height it
contributes, is not established here — it is the input most likely to interact badly with the
"height of the tallest argument" rule.
Errors
Microsoft's page for HSTACK documents #N/A as the pad value, which is a value in the
result rather than a failure of the call. The page does not publish a general error table.
#VALUE! for an inadmissible argument is the family's conventional code and is recorded here as
expected, not verified.
An error value supplied as an argument propagates under the universal rule in
coercion and lifting — as distinct from an error sitting
inside an argument array, which is stacked like any other value.
A result exceeding the grid's dimensions is a host limit rather than a function semantic; what
Excel returns at that limit is not established here.
A direct error stops these array functions; the same error in an array or cell is an element
An error written directly as an argument of these functions is the answer for the whole call. It is not placed in the result: HSTACK(1,#VALUE!) is #VALUE!, not a row holding 1 and an error, and TAKE(#N/A,0) is #N/A. With MAP the LAMBDA does not run at all: MAP(NA(),LAMBDA(xx,IFERROR(xx,3))) is #N/A. The same error inside an array, or in a referenced cell, is an ordinary element: MAP({#N/A},LAMBDA(xx,3)) is 3, and HSTACK(Data!C1,{1;2}) with #N/A in Data!C1 is a 2 by 2 array with #N/A in its first column. TOCOL and TOROW show the difference with ignore 2: TOCOL(#N/A,2) is #N/A, while TOCOL({#N/A},2) drops the error, has nothing left, and is #CALC!. EXPAND is an exception on the cell side: EXPAND(Data!C1,2,2,"pad") is #N/A for the whole call. BYROW and BYCOL check the direct error first too: BYROW(#N/A,#VALUE!) is #N/A, while BYROW({#N/A},2), where 2 is not a LAMBDA, is #VALUE!.
Formula
Excel
Mathematical value
=HSTACK(1,#VALUE!) Excel 16.0.20527.20088 Current Channel, Windows 64-bit (NT 10.0.26200), x86-64, AMD Ryzen 7 4800H, en-ZAInputs and reference value
Typed formula.
#VALUE!
—
=TAKE(#N/A,0) Excel 16.0.20527.20088 Current Channel, Windows 64-bit (NT 10.0.26200), x86-64, AMD Ryzen 7 4800H, en-ZAInputs and reference value
Typed formula.
#N/A
—
=MAP(NA(),LAMBDA(xx,IFERROR(xx,3))) Excel 16.0.20527.20088 Current Channel, Windows 64-bit (NT 10.0.26200), x86-64, AMD Ryzen 7 4800H, en-ZAInputs and reference value
Typed formula.
#N/A
—
=MAP({#N/A},LAMBDA(xx,3)) Excel 16.0.20527.20088 Current Channel, Windows 64-bit (NT 10.0.26200), x86-64, AMD Ryzen 7 4800H, en-ZAInputs and reference value
Typed formula.
3 0x4008000000000000
—
=HSTACK(Data!C1,{1;2}) Excel 16.0.20527.20088 Current Channel, Windows 64-bit (NT 10.0.26200), x86-64, AMD Ryzen 7 4800H, en-ZAInputs and reference value
Number supplied through a cell.
Data!C1: #N/A
{#N/A,1;#N/A,2}
—
=TOCOL(#N/A,2) Excel 16.0.20527.20088 Current Channel, Windows 64-bit (NT 10.0.26200), x86-64, AMD Ryzen 7 4800H, en-ZAInputs and reference value
Typed formula.
#N/A
—
=TOCOL({#N/A},2) Excel 16.0.20527.20088 Current Channel, Windows 64-bit (NT 10.0.26200), x86-64, AMD Ryzen 7 4800H, en-ZAInputs and reference value
Typed formula.
#CALC!
—
=EXPAND(Data!C1,2,2,"pad") Excel 16.0.20527.20088 Current Channel, Windows 64-bit (NT 10.0.26200), x86-64, AMD Ryzen 7 4800H, en-ZAInputs and reference value
Number supplied through a cell.
Data!C1: #N/A
#N/A
—
=BYROW(#N/A,#VALUE!) Excel 16.0.20527.20088 Current Channel, Windows 64-bit (NT 10.0.26200), x86-64, AMD Ryzen 7 4800H, en-ZAInputs and reference value
Typed formula.
#N/A
—
=BYROW({#N/A},2) Excel 16.0.20527.20088 Current Channel, Windows 64-bit (NT 10.0.26200), x86-64, AMD Ryzen 7 4800H, en-ZAInputs and reference value
The pad is per-argument, not per-result. Each argument is padded to the common height
independently; there is no single rectangle being filled. Implementations that build the
output row-major must know each source's height to decide where padding begins in that row.
Width is a sum and can overflow the grid. Validate against the host's column limit before
allocating, and decide what the failure looks like — the Handbook has not established Excel's
behaviour at the boundary.
Preserve element kinds exactly. Stacking is a copy, not a conversion; text, logicals and
errors must arrive unchanged. This is the property that lets HSTACK build heterogeneous
tables, and the easiest one to lose in an implementation that types its buffers numerically.
The identity case matters.HSTACK(x) with a single argument is a legitimate call — the
arity minimum is 1 — and it is used deliberately to materialize a reference as an array. It
must not be optimized into a pass-through that preserves reference-ness.
This function has its own module in the reference implementation rather than sharing the
reshape family's, which is a code-organization fact, not a behavioural one.
Rust code
Show the CalcFunc source of this function
Quoted from the public repository DnaCalc/DnaCalc, crates calc-func and calc-fml, at commit 9e1884369a67. Comments are the source's own. A kernel shared with other functions appears once, on its module page.
11 hstack_fn "HSTACK" (1, 254) [R"array1", R"array2"] |s, a, sv| {
// Fn2: a direct error in any source precedes stacking; reference/array cells remain cells.for source in a { scalar_error(source)?; }
// dc-h9u: a row of functions, as GROUPBY and PIVOTBY take them// (`GROUPBY(A2:A9,B2:B9,HSTACK(SUM,MAX))`, Excel 16.0.20527).if a.iter().all(|v| v.callable_value().is_some()) {
returnCalcArray::from_rows(vec![a.to_vec()])
.map(CalcValue::array)
.ok_or_else(|| s.not_modelled(None));
}
run(s, a, sv, k::eval_hstack_prepared)
}
/// HSTACK (hstack.rs): the arguments side by side, each padded with #N/A below to the tallest.pubfneval_hstack_prepared(args: &[CalcValue]) -> Result<CalcValue, DynamicArrayReshapeEvalError> {
let sources: Vec<StackArgSource<'_>> = args.iter().map(StackArgSource::new).collect();
let rows = sources.iter().map(|source| source.shape().rows).max().unwrap_or(1);
let cols: usize = sources.iter().map(|source| source.shape().cols).sum();
letmut cells = Vec::with_capacity(rows * cols);
for row in0..rows {
for source in &sources {
for col in0..source.shape().cols {
cells.push(
source
.get(row, col)
.cloned()
.unwrap_or(CalcValue::error(WorksheetErrorCode::NA)),
);
}
}
}
build_array(rows, cols, cells)
}
No implementation has been published with a Handbook test suite for this function.
Version differences
No comparison between specific Excel builds is recorded here yet. This does not establish that all builds behave alike.
Excel 16.0.20527.20088
Windows 64-bit (NT 10.0.26200), x86-64, AMD Ryzen 7 4800H, en-ZA
=HSTACK(1,#VALUE!) returned #VALUE!. =HSTACK(Data!C1,{1;2}) returned {#N/A,1;#N/A,2}. Inputs were supplied through cells using Value2, formula literals. These are controls from this environment, not a same-input comparison with another build.
VSTACK stacks vertically: widths are padded instead of heights, and the result's height
is the sum. The pair is symmetric, and HSTACK(VSTACK(…), …) compositions are how
dynamic-array formulas assemble tables.
EXPAND is how you control the padding rather than accept #N/A: expand each piece to the
common height with your own pad value first, then stack.
CHOOSECOLS is the inverse in spirit — it takes columns out of one array where HSTACK
puts columns from several together.
TOROW / TOCOL flatten rather than stack, and WRAPROWS / WRAPCOLS reshape a vector
into a rectangle; all four answer the neighbouring question of "how do I get this data into
that shape".
The & operator and TEXTJOIN concatenate values; HSTACK concatenates arrays. The
word "concatenate" pulls readers toward the wrong one.
INDEX / OFFSET gymnastics were how workbooks assembled multi-source arrays before the
stacking functions existed; HSTACK replaces most of that with a call whose intent is legible.