Both blocks are indexed and stored. The extending block which was received first is represented as an array element with links/pointers to the headers of the strong chain: Chains are represented as arrays with links pointing to headers in the store.
The second block is now considered part of a “candidate chain”, of which there can be many with different heights. The tip of each branch emanating from the strong chain is the tip of a candidate chain. A candidate chain can be potentially extended to form the strong chain if it accumulates the most POW. At this point, the strong chain array elements are popped to the forking height, and the new candidate chain headers/blocks are pushed on the strong chain array/stack. The popped elements of the strong chain are pushed to a candidate chain array in respective order.
Since the number of candidate chains is unbounded, it may not make sense to organize them individually to store, but keep a them as a separate data structure (tree). Furthermore, given the cost of auch branches at current chain heights, they tend to be short. The risk of reorg is most likely with the “2nd” highest chain, so keeping this in persistent store can optimize for faster reorgs if the node is restarted (other indexed candidate chains in memory are lost and need to be rebuilt, but less likely to overtake strong chain). Of course, an implementation can persistently index an arbitrary bounded number of candidate chains depending on design objective.