Individual qubits can be manipulated as isolated systems, using gates like Hadamard and Pauli-X, with results often visualized on the Bloch Sphere. The true power of quantum computing does not, however, lie in manipulating single particles in isolation. It arises when connecting multiple qubits to perform parallel operations. To describe a system with more than one qubit, a mathematical method is needed to combine their vector spaces. This method is the tensor product.Combining State SpacesIn classical computing, if you have two bits, the state of the system is described by the state of the first bit and the state of the second bit. For example, if bit A is 0 and bit B is 1, the system is in the state "01".In quantum mechanics, we cannot simply list the states side-by-side. Instead, we combine the vector space of the first qubit with the vector space of the second qubit to create a new, larger vector space. We use the tensor product operation, denoted by the symbol $\otimes$, to achieve this.If qubit $A$ is in state $|\psi\rangle$ and qubit $B$ is in state $|\phi\rangle$, the collective state of the system is written as:$$ |\psi\rangle \otimes |\phi\rangle $$Often, this is abbreviated as $|\psi\rangle|\phi\rangle$ or simply $|\psi\phi\rangle$.The Expansion of Basis StatesFor a single qubit, our standard basis vectors are $|0\rangle$ and $|1\rangle$. When we introduce a second qubit, we take the tensor product of every combination of these basis vectors. This results in four possible computational basis states for a two-qubit system:$$ |0\rangle \otimes |0\rangle = |00\rangle $$ $$ |0\rangle \otimes |1\rangle = |01\rangle $$ $$ |1\rangle \otimes |0\rangle = |10\rangle $$ $$ |1\rangle \otimes |1\rangle = |11\rangle $$Notice that a single qubit lives in a 2-dimensional complex vector space ($2^1$). A two-qubit system lives in a 4-dimensional space ($2^2$). This pattern continues exponentially. A three-qubit system lives in an 8-dimensional space ($2^3$). This exponential scaling is a fundamental characteristic of quantum systems.digraph G { rankdir=LR; node [shape=box, style=filled, fontname="Helvetica"]; subgraph cluster_0 { label = "Qubit 1 Basis"; color = "#dee2e6"; style = filled; node [fillcolor="#a5d8ff"]; q1_0 [label="|0⟩"]; q1_1 [label="|1⟩"]; } subgraph cluster_1 { label = "Qubit 2 Basis"; color = "#dee2e6"; style = filled; node [fillcolor="#b197fc"]; q2_0 [label="|0⟩"]; q2_1 [label="|1⟩"]; } subgraph cluster_2 { label = "Combined System (Tensor Product)"; color = "#e9ecef"; style = filled; node [fillcolor="#63e6be"]; q12_00 [label="|00⟩"]; q12_01 [label="|01⟩"]; q12_10 [label="|10⟩"]; q12_11 [label="|11⟩"]; } q1_0 -> q12_00 [color="#adb5bd"]; q2_0 -> q12_00 [color="#adb5bd"]; q1_0 -> q12_01 [color="#adb5bd"]; q2_1 -> q12_01 [color="#adb5bd"]; q1_1 -> q12_10 [color="#adb5bd"]; q2_0 -> q12_10 [color="#adb5bd"]; q1_1 -> q12_11 [color="#adb5bd"]; q2_1 -> q12_11 [color="#adb5bd"]; }Mapping individual qubit basis states to the composite state space basis.Calculating the Tensor ProductFor those more accustomed to linear algebra, the tensor product is best understood through its matrix representation, often called the Kronecker product.If we have two vectors, $A$ and $B$:$$ A = \begin{bmatrix} a_0 \ a_1 \end{bmatrix}, \quad B = \begin{bmatrix} b_0 \ b_1 \end{bmatrix} $$The tensor product $A \otimes B$ is calculated by multiplying each element of the first vector by the entire second vector:$$ A \otimes B = \begin{bmatrix} a_0 \cdot \begin{bmatrix} b_0 \ b_1 \end{bmatrix} \ a_1 \cdot \begin{bmatrix} b_0 \ b_1 \end{bmatrix} \end{bmatrix} = \begin{bmatrix} a_0 b_0 \ a_0 b_1 \ a_1 b_0 \ a_1 b_1 \end{bmatrix} $$Let us look at a concrete example. Suppose we have two qubits. The first is in state $|0\rangle$ and the second is in state $|1\rangle$. We want to find the vector representation of the combined state $|01\rangle$.Recall the column vectors for the basis states: $$ |0\rangle = \begin{bmatrix} 1 \ 0 \end{bmatrix}, \quad |1\rangle = \begin{bmatrix} 0 \ 1 \end{bmatrix} $$Now we apply the tensor product operation:$$ |0\rangle \otimes |1\rangle = \begin{bmatrix} 1 \ 0 \end{bmatrix} \otimes \begin{bmatrix} 0 \ 1 \end{bmatrix} = \begin{bmatrix} 1 \cdot \begin{bmatrix} 0 \ 1 \end{bmatrix} \ 0 \cdot \begin{bmatrix} 0 \ 1 \end{bmatrix} \end{bmatrix} = \begin{bmatrix} 1 \cdot 0 \ 1 \cdot 1 \ 0 \cdot 0 \ 0 \cdot 1 \end{bmatrix} = \begin{bmatrix} 0 \ 1 \ 0 \ 0 \end{bmatrix} $$The resulting vector $\begin{bmatrix} 0 & 1 & 0 & 0 \end{bmatrix}^T$ has a 1 in the second position (index 1), which corresponds to the binary value $01$.Visualizing Probability AmplitudesIn a single qubit system, we visualized states using two amplitudes ($\alpha$ and $\beta$). In a two-qubit system, our state vector contains four amplitudes. If the system is in a superposition, such as applying a Hadamard gate to both qubits, the state vector will have non-zero entries in all four positions.Consider the state where both qubits are in perfect superposition ($|+\rangle \otimes |+\rangle$). The resulting vector has a value of $0.5$ in every position (since $0.5^2 + 0.5^2 + 0.5^2 + 0.5^2 = 1$).{"layout": {"title": {"text": "Probability Distribution: Two Qubits in Superposition", "font": {"size": 16}}, "xaxis": {"title": "Basis States", "type": "category"}, "yaxis": {"title": "Probability (Amplitude Squared)", "range": [0, 0.5]}, "template": "simple_white", "width": 600, "height": 400}, "data": [{"x": ["|00⟩", "|01⟩", "|10⟩", "|11⟩"], "y": [0.25, 0.25, 0.25, 0.25], "type": "bar", "marker": {"color": ["#4dabf7", "#4dabf7", "#4dabf7", "#4dabf7"]}}]}Equal probability distribution for a two-qubit system after Hadamard gates are applied to both qubits.Implementation in PythonNumpy provides a direct method for calculating tensor products: np.kron. This function stands for the Kronecker product.When building quantum simulators from scratch, you will use this function frequently to combine state vectors and unitary matrices.import numpy as np # Define standard basis states as column vectors ket_0 = np.array([[1], [0]]) ket_1 = np.array([[0], [1]]) # Calculate |0⟩ ⊗ |1⟩ # Expected result: [0, 1, 0, 0]^T state_01 = np.kron(ket_0, ket_1) print(f"State |01⟩ vector:\n{state_01}") # Calculate |1⟩ ⊗ |1⟩ # Expected result: [0, 0, 0, 1]^T state_11 = np.kron(ket_1, ket_1) print(f"State |11⟩ vector:\n{state_11}")Why This MattersUnderstanding tensor products is the prerequisite for working with multi-qubit gates. Just as we expanded the state vectors, we must also expand our gate matrices. A single-qubit gate like the Pauli-X is a $2 \times 2$ matrix. To apply it to a two-qubit system, we must expand it into a $4 \times 4$ matrix using the tensor product.For example, if we want to apply an X gate to the second qubit while leaving the first qubit unchanged (applying the Identity matrix $I$), the operation on the full system is represented as $I \otimes X$.This mathematical framework allows us to define interactions where the state of one qubit influences another, which is the foundation for the CNOT gate and quantum entanglement discussed later in this chapter.