The data structure is a flat array of complex numbers, length 2^n for n qubits. Index it exactly like you'd index any bit-tricked flat array: the array index, read in binary, IS a basis state, where bit i tells you whether qubit i is |0> or |1> in that term. That's the whole "structure." Superposition isn't vibes — it's just: more than one entry of this array is nonzero at once.
State vector. For n qubits you need complex128 state[1 << n]. state[k] is the complex amplitude of basis state k (interpreting k's bits as each qubit's value). The physical meaning of an amplitude is: |state[k]|^2 is the probability of measuring the system and getting bitstring k. The array must satisfy sum(|state[k]|^2 for all k) == 1 — that's the normalization constraint, and it's the entire content of "the qubits are in superposition": probability mass spread across multiple array slots simultaneously, with complex amplitudes (not just probabilities) so they can interfere (cancel or reinforce) when you apply gates.
Initialize to the all-zero state: state[0] = 1+0j, everything else 0. That's "n qubits, all set to |0>", exactly analogous to initializing an int to 0.
Applying a single-qubit gate to qubit t. A 1-qubit gate is a 2x2 complex matrix, e.g. Hadamard:
H = 1/sqrt(2) * [[1, 1],
[1, -1]]
To apply it to qubit t, you go over the array in pairs: every pair of indices that differ only in bit t gets mixed together by the matrix, independent of what all the other bits are. In code:
c
void apply_1q_gate(complex128 *state, int n, int t, complex128 gate[2][2]) {
int bit = 1 << t;
for (int i = 0; i < (1 << n); i++) {
if ((i & bit) == 0) { // process each pair once, at its "0" index
int j = i | bit; // the paired index with qubit t flipped to 1
complex128 a0 = state[i];
complex128 a1 = state[j];
state[i] = gate[0][0]*a0 + gate[0][1]*a1;
state[j] = gate[1][0]*a0 + gate[1][1]*a1;
}
}
}
That's it — that's the "actual loop." i & bit == 0 picks out one representative of each pair (the version with qubit t = 0); j = i | bit is its partner (qubit t = 1). You read both amplitudes, and write back the matrix-vector product, exactly like a 2-element FFT butterfly (it genuinely is the same "butterfly" access pattern as an FFT, if that connects for you).
Applying Hadamard to qubit 0 on a freshly-initialized single qubit turns [1, 0] into [1/sqrt2, 1/sqrt2] — now two array slots are nonzero. That's the entire mechanism of "putting a qubit into superposition": running it through a linear transform whose output has more than one nonzero component. No mysticism, just a matrix-vector multiply applied selectively across index pairs.
Two-qubit gates (like CNOT) work the same way but group indices into quadruples that differ only in the two relevant bits, and multiply by a 4x4 matrix. CNOT specifically is even simpler to special-case: it doesn't need real arithmetic at all, just a conditional swap —
c
void apply_cnot(complex128 *state, int n, int control, int target) {
int cbit = 1 << control, tbit = 1 << target;
for (int i = 0; i < (1 << n); i++) {
if ((i & cbit) && !(i & tbit)) {
int j = i | tbit;
complex128 tmp = state[i];
state[i] = state[j];
state[j] = tmp;
}
}
}
Swap amplitudes between i and i with the target bit flipped, but only among indices where the control bit is set. Same bit-trick indexing as your interpreter work, just gating the swap on a bitmask condition.
Measurement. Compute p[k] = |state[k]|^2 for all k (or just the ones consistent with earlier partial measurements), sample an index according to that probability distribution, and then collapse: zero out every amplitude except those consistent with the observed outcome, and renormalize (divide by sqrt of the total remaining probability mass) so the vector has unit norm again.
Why this is only good for "a few qubits": the array is 2^n complex numbers. At n=20 that's a million entries, fine. At n=40 that's ~10^12, not fine. This is the actual, physical reason full state-vector simulation is a toy for small n and real quantum computers matter — the classical resource cost is exponential in the number of qubits by construction, since you're explicitly storing every amplitude rather than exploiting whatever structure a physical device gets "for free." But for education and for the qubit counts you'll simulate on a laptop, this flat array plus bit-indexed pairwise loop is the entire mechanism — there's no additional hidden machinery beneath "superposition."