AlphaGo and AlphaZero

In 1997 a computer beat the world chess champion. Yet for another eighteen years, the ancient game of Go resisted machines. Go's branching factor is roughly 250 legal moves per position, against chess's 35. This made brute-force search hopeless. No simple evaluation function could judge who was winning on a 19×19 board. The problem demanded something that could combine intuition — pattern recognition over an astronomically large state space — with the disciplined lookahead of search. This is exactly the gap that Planning and Monte-Carlo Tree Search and Deep Policy Gradient Methods fill separately. AlphaGo filled it by fusing them. AlphaGo, introduced by DeepMind in 2016, defeated the professional Go player Lee Sedol 4–1. Many experts had predicted this result was a decade away. Its successor AlphaZero, published in 2017, went further. Given only the rules of Go, chess, or shogi, it learned superhuman play from scratch. It did this through self-play, with no human game data. It used no domain-specific heuristics beyond the rules themselves. The same architecture that mastered Go also became the strongest chess and shogi engine in the world. It did so within hours of training. This overturned decades of hand-tuned chess engine design. The core idea threads together three techniques covered elsewhere in this curriculum. First, a deep neural network that outputs both a move distribution (policy) and a position evaluation (value). Second, Monte-Carlo Tree Search, used not as a standalone planner but as a policy-improvement operator. Third, self-play reinforcement learning, which generates its own training data. It does this by having the network play against itself. Each component compensates for the others' weaknesses. The network provides fast, learned intuition where raw search would drown in branching factor. The tree search corrects the network's errors by looking ahead. This produces training targets better than the network's raw output. This article gives the full mechanics. It covers how the policy/value network is structured and trained. It explains how MCTS uses the network to guide a much narrower and deeper search than classical Monte-Carlo rollout methods. It describes how the self-play loop bootstraps from random play to superhuman strength. It closes with the complexity accounting, worked traces of tree search and self-play update. It also traces the historical arc from AlphaGo's initial supervised bootstrap to AlphaZero's from-scratch generality.

Key takeaways
  • AlphaGo combines a policy network, a value network, and Monte-Carlo Tree Search (MCTS) to play Go at superhuman level.
  • MCTS is guided by the network's policy priors (which moves to explore) and value estimates (replacing random rollouts).
  • Self-play generates training data: games played by the current network against itself, with search-improved move distributions as labels.
  • AlphaZero unifies policy and value into a single network and removes all human data, learning chess, shogi, and Go from raw rules alone.
  • The MCTS "policy improvement operator" makes the search output a strictly better policy than the network alone, which is the crux of why the loop converges.
  • Superhuman results emerged from tractable engineering: distillation of tree search into a small network, letting inference stay fast despite deep search during training.
The core insight

AlphaZero treats Monte-Carlo Tree Search as a subroutine that turns an imperfect policy/value network into a better one, and trains the network to imitate that improved output through self-play — turning search into a data-generating teacher for learning.

The idea

AlphaGo and AlphaZero are built on a policy/value network fθ(s)=(p,v)f_\theta(s) = (p, v) that maps a board state ss to a probability distribution pp over legal moves (the policy) and a scalar v∈[−1,1]v \in [-1, 1] estimating the expected outcome for the current player (the value). This single network replaces two separate estimators used in classical game-playing systems: a move generator and a static evaluation function. Because Go and chess have far too many states to tabulate, the network is a deep convolutional (AlphaGo) or residual (AlphaZero) architecture that generalizes across positions it has never seen exactly. The second ingredient is Monte-Carlo Tree Search used as policy improvement. Classical MCTS estimates the value of moves by running many random rollouts to game end and averaging outcomes — this is Planning and Monte-Carlo Tree Search in its vanilla form. AlphaGo/AlphaZero instead use the network's value vv in place of random rollouts (AlphaGo blends both; AlphaZero uses only the network) and use the network's policy pp to bias which branches get explored, via a PUCT (Predictor + Upper Confidence bound applied to Trees) formula. The tree search, run for a fixed simulation budget from the current position, produces a refined visit-count distribution π\pi over moves that is demonstrably better than the raw network policy pp — this refinement is the mechanism that drives learning. The third ingredient is self-play reinforcement learning. The network plays complete games against itself, at each move running MCTS to produce the improved policy π\pi and eventually observing a game outcome z∈{−1,0,+1}z \in \{-1, 0, +1\}. These (s,π,z)(s, \pi, z) triples become training data: the network is updated to make its raw policy pp match the search-improved π\pi, and its value vv match the actual outcome zz. Because π\pi is a better policy than pp, training the network toward π\pi improves the network, which in turn makes future MCTS searches even stronger — a policy iteration loop, related in spirit to Deep Policy Gradient Methods but distinguished by using search rather than gradient ascent on return as the improvement operator.

How it works

Network architecture. AlphaGo used two separate deep convolutional networks (SL/RL policy networks and a value network), each trained on stacks of feature planes encoding the board (stone positions, liberties, ko status, move history). AlphaZero unified these into a single network with a shared "trunk" of residual blocks and two output "heads": a policy head producing a distribution over moves, and a value head producing a scalar. Sharing the trunk means the network learns representations useful for both tasks jointly, and halves the parameter count and inference cost relative to two separate networks.

MCTS with network guidance. Each simulation traverses the tree from the root using a selection rule that balances exploitation of high-value moves against exploration of moves the policy network favors but that have not been tried often:

at=arg⁡max⁡a[Q(s,a)+cpuct⋅P(s,a)⋅∑bN(s,b)1+N(s,a)]a_t = \arg\max_a \left[ Q(s,a) + c_{\text{puct}} \cdot P(s,a) \cdot \frac{\sqrt{\sum_b N(s,b)}}{1 + N(s,a)} \right]

Here Q(s,a)Q(s,a) is the mean value backed up from simulations through that edge, P(s,a)P(s,a) is the network's prior probability for move aa (encouraging exploration proportional to how promising the policy thinks the move is), and N(s,a)N(s,a) is the visit count (the bonus term shrinks as a move is tried more, forcing exploration of under-visited alternatives). This is a variant of the standard Planning and Monte-Carlo Tree Search UCB1 formula adapted to incorporate a learned prior — hence "PUCT." When a simulation reaches a leaf (a state not yet expanded), the network evaluates it once: p,v=fθ(sleaf)p, v = f_\theta(s_{\text{leaf}}). The leaf is expanded with children priors P(s,⋅)=pP(s,\cdot) = p, and the value vv is backed up along the path traversed, updating QQ and NN for every edge visited. No random rollout to the end of the game is performed in AlphaZero — the value network's estimate substitutes for it entirely, which is the key departure from vanilla Monte-Carlo Tree Search. After a fixed number of simulations (e.g. 800 in AlphaZero's self-play), the root's visit counts define the improved policy:

π(a∣s)=N(s,a)1/τ∑bN(s,b)1/τ\pi(a \mid s) = \frac{N(s,a)^{1/\tau}}{\sum_b N(s,b)^{1/\tau}}

with temperature τ\tau controlling exploration (high early in a game for training diversity, near zero for competitive play).

Self-play training loop. The network plays against itself using π\pi to select moves (sampled, not argmax, especially early in training), storing (st,πt)(s_t, \pi_t) at every move. At the game's end the outcome zz (from the perspective of the player to move at each sts_t) is recorded for every stored state. A replay buffer of recent self-play games supplies minibatches to a stochastic gradient descent step minimizing:

L(θ)=(z−v)2−π⊤log⁡p+c∥θ∥2L(\theta) = (z - v)^2 - \pi^\top \log p + c \lVert \theta \rVert^2

The first term drives the value head toward the game outcome (a regression loss), the second is a cross-entropy loss driving the policy head toward the search-improved distribution, and the last is L2 regularization. Crucially, both signals come from the same self-play games — there is no separate labeled dataset (in AlphaZero; AlphaGo's first stage used human game records to bootstrap the policy network before self-play took over). ```text function ALPHAZERO_TRAIN(iterations, games_per_iter, sims_per_move): theta ← random initialization replay_buffer ← empty for i in 1..iterations:

# --- Self-play phase --- for g in 1..games_per_iter: s ← initial_state history ← [] while not TERMINAL(s): pi ← MCTS_SEARCH(s, theta, sims_per_move) history.append((s, pi)) a ← SAMPLE(pi) # temperature-controlled s ← APPLY(s, a) z ← OUTCOME(s) # +1 win / -1 loss / 0 draw for (s_t, pi_t) in history: z_t ← z if PLAYER(s_t) == winner else -z replay_buffer.add(s_t, pi_t, z_t)

# --- Training phase --- for step in 1..training_steps: batch ← SAMPLE_MINIBATCH(replay_buffer) loss ← 0 for (s, pi, z) in batch: p, v ← NETWORK(s, theta) loss += (z - v)^2 - dot(pi, log(p)) + c L2(theta) theta ← theta - lr GRADIENT(loss, theta)

# --- Evaluation (AlphaGo Zero variant) --- if EVALUATE(theta_new) beats theta_current by margin: theta_current ← theta_new # else keep old network return theta_current

function MCTS_SEARCH(s_root, theta, num_simulations): tree ← {s_root: new node} for i in 1..num_simulations: s ← s_root path ← [s]

# Selection: descend using PUCT until a leaf/unexpanded node while tree[s].is_expanded: a ← argmax_a [ Q(s,a) + c_puct P(s,a) sqrt(sum_b N(s,b)) / (1 + N(s,a)) ] s ← APPLY(s, a) path.append(s)

# Expansion + evaluation if TERMINAL(s): v ← OUTCOME(s) else: p, v ← NETWORK(s, theta) EXPAND(tree, s, priors = p)

# Backup for s_i in reversed(path): N(s_i, a_i) += 1 Q(s_i, a_i) += (v - Q(s_i, a_i)) / N(s_i, a_i) v ← -v # value flips sign for the opposing player return normalize(N(s_root, ·)) # visit-count distribution = pi ```

Figure
The self-play policy-improvement loop: network guides search, search improves network
Network fθ policy p(a|s) value v(s) MCTS (PUCT) uses p as prior, v to back up leaves Self-play game produces (s, π, z) π = search-improved policy SGD update p → π (cross-entropy) v → z (MSE) (p, v) π, moves (s, π, z) θ ← θ − lr·∇L Loop: better network → deeper effective search → better π → better network

Complexity & correctness

Per-move inference cost. Each MCTS simulation performs one tree descent (depth dd, the length of the game path so far, bounded by the game's maximum length) and one network evaluation at the leaf. With nn simulations per move, the search costs O(n⋅d)O(n \cdot d) tree operations plus nn network forward passes. Since dd is typically small relative to nn (a few dozen moves deep versus hundreds of simulations), the dominant cost is nn network evaluations, each O(1)O(1) in the number of layers times input size — practically, a fixed-cost GPU/TPU forward pass. AlphaGo Zero used n=1600n=1600 simulations per move during self-play; AlphaZero used n=800n=800.

Space. The search tree stores one node per visited state, at most O(n)O(n) nodes per move since each simulation can create at most one new node. Each node stores O(∣A(s)∣)O(|A(s)|) statistics (NN, QQ, PP per legal action), so total space per search is O(n⋅∣A∣max⁡)O(n \cdot |A|_{\max}), where ∣A∣max⁡≈250|A|_{\max} \approx 250 for Go, or the maximum branching factor.

Training cost. Each self-play game of length TT moves costs O(T⋅n)O(T \cdot n) network evaluations to generate, and produces TT training examples. Training itself is standard supervised learning cost: O(batch size×network size)O(\text{batch size} \times \text{network size}) per gradient step. The total computational cost is dominated by self-play game generation, which is why AlphaGo Zero's reported training used thousands of TPUs generating millions of self-play games in parallel — the wall-clock bottleneck is data generation, not gradient descent.

Why the policy-improvement loop converges (correctness argument). The central claim is that MCTS is a policy improvement operator: given any network policy pp and value vv as priors, running enough simulations of PUCT search yields a visit distribution π\pi whose expected value (played against a fixed opponent, or in self-play) is at least as good as pp's, in the same sense that greedy improvement over an approximate value function improves a policy in classical policy iteration (compare Planning and Monte-Carlo Tree Search as an approximate best-response computation). Intuitively:

  • If vv is a decent estimate of position value and pp concentrates prior probability on reasonable moves, then PUCT allocates most simulations to moves with the highest estimated QQ, refining Q(s,a)Q(s,a) toward the true minimax value of aa as n→∞n \to \infty, since PUCT search is proven (in the tabular limit) to converge to the game-theoretic optimal value, much like UCB1-based MCTS converges to minimax play with enough samples.
  • Training the network to imitate π\pi (rather than to imitate pp itself, which would be a no-op) is therefore training toward a genuinely improved target — this is analogous to policy iteration: evaluate (via search), improve (network imitates search), repeat.
  • This resembles Deep Policy Gradient Methods in using self-generated experience to improve a policy, but differs fundamentally: the improvement signal here is not a return-based policy gradient but a distillation target produced by explicit lookahead, which tends to be lower-variance and more sample-efficient because search amplifies a mediocre prior into a much stronger target before any gradient step happens.

Tradeoffs, honestly stated. The loop's convergence is empirical and not accompanied by a full convergence proof for the deep-network case (unlike tabular policy iteration, which is provably monotonic). Function approximation, non-stationarity of the self-play opponent, and the finite simulation budget mean genuine improvement each iteration is a strong empirical regularity rather than a guarantee — occasional regressions (the network briefly getting worse) are handled by explicit evaluation gating in AlphaGo Zero (only replace the current best network if the challenger wins a majority of an evaluation match), a safeguard AlphaZero later relaxed for continuous self-play without gating, trading strict monotonic improvement for engineering simplicity and slightly faster overall progress.

Worked example

Consider a single MCTS simulation step from a simplified position with three legal moves {A,B,C}\{A, B, C\}, network priors P=(0.5,0.3,0.2)P = (0.5, 0.3, 0.2), and cpuct=1.5c_{\text{puct}} = 1.5, starting from an empty tree (all N=0N=0, Q=0Q=0).

SimulationSelection rule value for A, B, CMove chosenLeaf evaluation vvBackup
1Q=0 for all; PUCT score = c⋅P⋅0/(1)=0c \cdot P \cdot \sqrt{0}/(1)=0 for all → tie broken by highest priorAnetwork returns v=+0.4v=+0.4N(A)=1N(A){=}1, Q(A)=0.4Q(A){=}0.4
2Q(A)=0.4Q(A)=0.4, PUCT(A)=0.4+1.5⋅0.5⋅1/2=0.7750.4{+}1.5{\cdot}0.5{\cdot}\sqrt1/2{=}0.775; PUCT(B)=0+1.5⋅0.3⋅1=0.450{+}1.5{\cdot}0.3{\cdot}1{=}0.45; PUCT(C)=1.5⋅0.2=0.31.5{\cdot}0.2{=}0.3Av=+0.2v=+0.2N(A)=2N(A){=}2, Q(A)=0.3Q(A){=}0.3
3PUCT(A)=0.3+1.5⋅0.5⋅2/3=0.6540.3{+}1.5{\cdot}0.5{\cdot}\sqrt2/3{=}0.654; PUCT(B)=1.5⋅0.3⋅2=0.6361.5{\cdot}0.3{\cdot}\sqrt2{=}0.636; PUCT(C)=1.5⋅0.2⋅2=0.4241.5{\cdot}0.2{\cdot}\sqrt2{=}0.424Av=−0.1v=-0.1N(A)=3N(A){=}3, Q(A)=0.167Q(A){=}0.167
4PUCT(A)=0.167+1.5⋅0.5⋅3/4=0.4920.167{+}1.5{\cdot}0.5{\cdot}\sqrt3/4{=}0.492; PUCT(B)=0+1.5⋅0.3⋅3=0.7790{+}1.5{\cdot}0.3{\cdot}\sqrt3{=}0.779Bv=+0.6v=+0.6N(B)=1N(B){=}1, Q(B)=0.6Q(B){=}0.6
5PUCT(A)=0.167+1.5⋅0.5⋅4/4=0.5420.167{+}1.5{\cdot}0.5{\cdot}\sqrt4/4=0.542; PUCT(B)=0.6+1.5⋅0.3⋅4/2=0.750.6{+}1.5{\cdot}0.3{\cdot}\sqrt4/2=0.75Bv=+0.5v=+0.5N(B)=2N(B){=}2, Q(B)=0.55Q(B){=}0.55

After 5 simulations: N(A)=3,N(B)=2,N(C)=0N(A)=3, N(B)=2, N(C)=0. Normalizing visit counts (temperature τ=1\tau=1) gives the improved policy π=(0.6,0.4,0.0)\pi = (0.6, 0.4, 0.0) — notice this differs sharply from the raw prior P=(0.5,0.3,0.2)P=(0.5, 0.3, 0.2): move CC, initially given 20% prior weight, received zero search visits because early evaluations of AA and BB looked good enough that PUCT never found it worth exploring within this tiny budget, while BB's strong evaluation (v=0.6v=0.6) pulled visits toward it despite its lower prior. This concretely shows how a handful of simulations can already redistribute probability mass away from the network's naive guess — the exact quantity used as a training target for the next gradient step. A second, larger-scale worked fact from AlphaGo Zero's published results: after roughly 4.9 million self-play games (using 1600 simulations per move), the trained network alone (zero search) played at a strength exceeding AlphaGo Lee (the version that beat Lee Sedol) with full tree search, illustrating how much game-strength gets distilled into the raw network weights through repeated policy-improvement cycles — the network head becomes, in effect, a compressed cache of many nights of tree search.

Common pitfalls & traps

PitfallWhy it happensFix
Treating MCTS visit counts as certainties at low simulation budgetsFew simulations (as in the worked example) can starve promising but unlucky-looking moves of visitsUse enough simulations, and add exploration noise (Dirichlet noise at the root) so early evaluations don't permanently bias search
Forgetting to flip the value sign on backupValue is always from the perspective of the player to move; alternating turns means the sign must flip each plyExplicitly negate vv at each backup step, as in the pseudocode
Training instability from a moving self-play opponentThe opponent (a past or current version of the same network) changes as training proceeds, unlike i.i.d. supervised dataMaintain a replay buffer over many recent iterations, not just the latest games; use evaluation gating
Ignoring the resign threshold problemWithout early resignation, self-play wastes computation finishing already-decided games; but resigning too aggressively creates false losses that corrupt trainingUse resignation only above a confidence threshold, validated periodically by continuing a sample of resigned games to completion
Confusing AlphaGo (uses rollouts + human data) with AlphaZero (no rollouts, no human data)The two systems share an architecture family but differ in training regimeBe precise: AlphaGo Fan/Lee used supervised pretraining on human games and rollout-based value estimation; AlphaGo Zero and AlphaZero use none of that

Pioneers & history

Origins · 1 minDavid Silver and how this was discovered
DS
David Silver
1976– · DeepMind, University College London

David Silver led the AlphaGo and AlphaZero research programs at DeepMind, building directly on his earlier doctoral work on Monte-Carlo planning and temporal-difference learning in games. His research united Planning and Monte-Carlo Tree Search with deep function approximation at a scale no prior system had attempted, turning what had been separate subfields — search-based game AI and deep reinforcement learning — into a single, generalizable architecture applicable to any two-player perfect-information game.

History
1968–1994
Computer Go programs based on hand-crafted heuristics remain far below amateur human level, unlike chess engines of the same era.
2006
Kocsis and Szepesvári formalize UCT (Upper Confidence bounds applied to Trees), giving Monte-Carlo Tree Search a principled exploration rule and enabling the first competent Go programs.
2016
AlphaGo defeats Lee Sedol 4–1 in a five-game match in Seoul, the first superhuman defeat of a top professional Go player by a machine.
2017
AlphaGo Zero surpasses all previous AlphaGo versions using only self-play and the rules of Go, with no human game data.
2017–2018
AlphaZero generalizes the architecture to chess and shogi, defeating the strongest existing programs (Stockfish, elmo) after training from random play in under 24 hours.

Where this matters

The AlphaGo/AlphaZero architecture reshaped both game AI and the broader deep reinforcement learning field. Its direct descendant MuZero extended the same policy-improvement-via-search idea to settings where the rules of the environment are not known in advance, learning an internal model instead — relevant to any planning problem where a simulator is unavailable. Modern chess engines (including open-source projects like Leela Chess Zero) directly reimplement the AlphaZero training loop, and the technique has been applied outside games to combinatorial optimization problems (chip placement, molecule design) where a policy/value network guided by tree search proposes and evaluates candidate solutions. More broadly, the idea that search can serve as a teacher for a neural network — producing better-than-current-policy training targets on the fly — has influenced the design of self-improving systems well beyond board games, including some approaches to program synthesis and automated theorem proving where a similar look-ahead-then-distill loop is used to bootstrap capability without external supervision.

Ask the tutor: How exactly does the PUCT formula's exploration term change behavior as simulation count grows large, and why does that guarantee the search eventually favors the truly best move even if the prior initially favored a worse one?

Does AlphaZero use any human knowledge of Go or chess strategy at all?

AlphaZero uses only the rules of the game — legal moves, whose turn it is, and how the game ends — encoded as the input representation and legal-move mask. It receives no human game records, no opening books, no hand-crafted evaluation features (such as chess's material-value heuristics), and no endgame tablebases. All strategic knowledge, including concepts analogous to material advantage or positional control, emerges purely from self-play and the policy-improvement loop. This is the key distinction from AlphaGo, whose original policy network was pretrained via supervised learning on a database of human professional games before self-play refinement began.

Why does MCTS need a learned prior instead of just exploring uniformly, as vanilla UCT does?

Uniform exploration works reasonably in domains with small branching factors, like the games UCT was originally designed and analyzed for. In Go, with roughly 250 legal moves per position, uniform exploration wastes almost all simulations on moves that are obviously bad to any competent player. The policy network's prior P(s,a)P(s,a) concentrates the search budget on plausible moves immediately, letting a fixed simulation budget achieve effectively much deeper, more relevant search. This is precisely why AlphaGo could search only a few hundred to a couple thousand simulations per move — far fewer than classical Go programs' rollout counts — and still play far stronger.

Is the "value network" learning to predict the winner, or something else?

The value head is trained to predict the expected game outcome z∈{−1,0,+1}z \in \{-1,0,+1\} from the current player's perspective, given the state. It is a regression target learned via mean-squared error against the actual self-play game outcome, not a hand-designed evaluation function. Because zz is a noisy, high-variance signal — only observed once per game rather than at every state — the value head effectively learns a smoothed approximation of "probability of winning" by averaging over millions of self-play trajectories that pass through similar states.

How is this related to policy gradient methods like REINFORCE or PPO?

Both families are on-policy reinforcement learning: the agent generates its own data by acting in the environment (or, here, by self-play) and updates its policy from that data, connecting directly to Deep Policy Gradient Methods. The difference is the improvement mechanism: policy gradient methods increase the log-probability of actions proportionally to an advantage estimate, using calculus on the return. AlphaZero instead computes an improved target distribution through explicit tree search, then trains the network via supervised cross-entropy toward that target. The search-based approach tends to be more sample-efficient per game because it extracts a much richer, lower-variance training signal (an entire distribution over moves, backed by lookahead) rather than a single scalar advantage.

Why does AlphaZero work for chess and shogi as well as Go, given how different these games are?

The architecture makes almost no game-specific assumptions: the input is a stack of feature planes describing the current board and recent history, and the output is a policy over a fixed, game-specific move encoding plus a scalar value. The MCTS, PUCT formula, and self-play training loop are entirely game-agnostic. Only the input/output encoding and the rules engine (move generation, terminal detection) change between games. This generality is a central point of the AlphaZero paper: the same hyperparameters and training procedure achieved superhuman results in three structurally different games without game-specific tuning.

What happens if the network is wrong about a position — does MCTS just propagate the error?

If the leaf evaluation vv is inaccurate, that error does get backed up and initially biases QQ estimates near that leaf. However, as more simulations are run, PUCT allocates increasing visits to moves whose subtrees reveal, through further network evaluations at deeper leaves, a more accurate picture — search averages over many separate (and only weakly correlated) network evaluations rather than trusting a single one. This is analogous to how ensemble averaging reduces variance: any single evaluation can be wrong, but the aggregated visit-count distribution over hundreds of simulations is empirically far more reliable than the raw one-shot network output, which is exactly why π\pi outperforms pp as a training target.

Does the training loop have any convergence guarantee, or is it purely empirical?

In the idealized tabular limit — infinite simulations per move, and a policy/value table rather than a neural network — PUCT-style MCTS is known to converge to correct minimax values, giving a rigorous foundation similar to that of policy iteration in Planning and Monte-Carlo Tree Search. Once function approximation, finite simulation budgets, and a constantly-updating self-play opponent enter the picture, no comparable formal guarantee exists; AlphaGo Zero's evaluation gating and the sheer empirical scale of testing (millions of self-play games) are the practical substitutes for a missing proof, and occasional regressions are an accepted, monitored risk rather than a theoretical impossibility.

Before this