I am doing a personal project, where at one point I need to validate the FEN position, I started with some basics checks, such as check if there are kings and making sure there aren't any extra rows or columns, and that kind of things.

But what other checks should I do to completely make sure a FEN is legal?


Here is a well organized list that should validate 99.99%+ of common positions:


  • There are exactly 8 cols
  • The sum of the empty squares and pieces add to 8 for each rank (row)
  • There are no consecutive numbers for empty squares


  • See if there is exactly one w_king and one b_king
  • Make sure kings are separated 1 square apart


  • Non-active color is not in check
  • Active color is checked less than 3 times (triple check is impossible); in case of 2 that it is never pawn+(pawn, bishop, knight), bishop+bishop, knight+knight


  • There are no more than 8 pawns from each color
  • There aren't any pawns in first or last rank (row) since they're either in a wrong start position or they should have promoted.
  • In case of en passant square; see if it was legally created (e.g it must be on the x3 or x6 rank, there must be a pawn (from the correct color) in front of it, and the en passant square and the one behind it are empty)
  • Prevent having more promoted pieces than missing pawns (e.g extra_pieces = Math.max(0, num_queens-1) + Math.max(0, num_rooks-2)... and then extra_pieces <= (8-num_pawns)), also you should do special calculations for bishops, If you have two (or more) bishops from the same square color, these can only be created through pawn promotion and you should include this information to the formula above somehow
  • The pawn formation is possible to reach (e.g in case of multiple pawns in a single col, there must be enough enemy pieces missing to make that formation), here are some useful rules:
    1. it is impossible to have more than 6 pawns in a single file (column) (because pawns can't exist in the first and last ranks)
    2. the minimum number of enemy missing pieces to reach a multiple pawn in a single col B to G 2=1, 3=2, 4=4, 5=6, 6=9 ___ A and H 2=1, 3=3, 4=6, 5=10, 6=15, for example, if you see 5 pawns in A or H, the other player must be missing at least 10 pieces from his 15 captureable pieces
    3. if there are white pawns in a2 and a3, there can't legally be one in b2, and this idea can be further expanded to cover more possibilities


  • If the king or rooks are not in their starting position; the castling ability for that side is lost (in the case of king, both are lost)


  • Look for bishops in the first and last ranks (rows) trapped by pawns that haven't moved, for example:
    1. a bishop (any color) trapped behind 3 pawns
    2. a bishop trapped behind 2 non-enemy pawns (not by enemy pawns because we can reach that position by underpromoting pawns, however if we check the number of pawns and extra_pieces we could determine if this case is possible or not)


  • (Avoid this if you want to validate Fisher's Chess960) If there are non-jumpers enemy pieces in between the king and rook and there are still some pawns without moving; check if these enemy pieces could have legally gotten in there. Also, ask yourself: was the king or rook needed to move to generate that position? (if yes, we need to make sure the castling abilities reflect this)
  • If all 8 pawns are still in the starting position, all the non-jumpers must not have left their initial rank (also non-jumpers enemy pieces can't possibly have entered legally), there are other similar ideas, like if the white h-pawn moved once, the rooks should still be trapped inside the pawn formation, etc.

Half/Full move Clocks:

  • In case of an en passant square, the half move clock must equal to 0
  • HalfMoves <= ((FullMoves-1)*2)+(if BlackToMove 1 else 0), the +1 or +0 depends on the side to move
  • The HalfMoves must be x >= 0 and the FullMoves x >= 1


  • Make sure the FEN contains all the parts that are needed (e.g active color, castling ability, en passant square, etc)

Note: there is no need to make the 'players should not have more than 16 pieces' check because the points 'no more than 8 pawns' + 'prevent extra promoted pieces' + the 'exactly one king' should already cover this point

Note2: these rules are intended to validate positions arising from the starting position of normal chess, some of the rules will invalidate some positions from Chess960 (exception if started from arrangement Nº518) and generated puzzles so avoid them to get a functional validator.

  • 1
    You can also check the pawn structure, for example white pawns could never be on a2, a3, and b2; there is no way a pawn could be on both a3 and b2. – Akavall Nov 3 '12 at 14:19
  • Is that to say FEN positions should only be achievable from the initial position? What if I wanted to have puzzle positions represented by a FEN? Sometimes they are created in a way impossible to reach in an actual game... – tbischel Jun 24 '13 at 18:45
  • @tbischel I making these rules from the normal chess perspective (not intended for Chess960 or other generated positions), thanks I might point this somewhere to make it clearer – ajax333221 Jun 24 '13 at 19:27
  • 1
    @ajax333221 : ​ This page gives legal games in which white gets more than 5 pawns on the a file. ​ ​ ​ ​ – user2668 Aug 21 '16 at 19:02
  • 1
    "Make sure kings are separated 1 square apart" I think this requirement can be removed because it is included in the next one: "Non-active color is not in check" – Evargalo Oct 17 '19 at 12:22

Here's a regular expression that I use to ensure that a FEN string is actually valid. It doesn't do any testing for a legal/illegal position, but it's a good starting point.

| improve this answer | |
  • I think the active color is a must (you are allowing -) and half/full clocks are sometimes optional I think. Also I didn't understand the a-h part on castling ability, I rewrote it to /^\s*([rnbqkpRNBQKP1-8]+\/){7}([rnbqkpRNBQKP1-8]+)\s[bw]\s(-|K?Q?k?q?)\s(-|[a-h][36])/ – ajax333221 Mar 7 '13 at 23:55
  • I just noted we can do the "no pawns in promotion ranks" test with something starting like ([rnbqkRNBQK1-8]+\/)([rnbqkpRNBQKP1-8]+\/){6}([rnbqkRNBQK1-8]+) .... – ajax333221 Jul 4 '13 at 17:48
  • also for the clocks this might be good (0|[1-9][0-9]*)\s([1-9][0-9]*) as moves can't have leading zeros and full move can't be or start with 0, (code credit) – ajax333221 Sep 16 '13 at 19:48

For the others, there is a simple function in Stockfish engine, that validates a FEN String.

bool Position::is_valid_fen(const std::string &fen) {
   std::istringstream iss(fen);
   std::string board, side, castleRights, ep;

   if (!iss) return false;

   iss >> board;

   if (!iss) return false;

   iss >> side;

   if (!iss) {
      castleRights = "-";
      ep = "-";
   } else {
      iss >> castleRights;
      if (iss)
         iss >> ep;
         ep = "-";

   // Let's check that all components of the supposed FEN are OK.
   if (side != "w" && side != "b") return false;
   if (castleRights != "-" && castleRights != "K" && castleRights != "Kk"
       && castleRights != "Kkq" && castleRights != "Kq" && castleRights !="KQ"
       && castleRights != "KQk" && castleRights != "KQq" && castleRights != "KQkq"
       && castleRights != "k" && castleRights != "q" && castleRights != "kq"
       && castleRights != "Q" && castleRights != "Qk" && castleRights != "Qq"
       && castleRights != "Qkq")
      return false;
   if (ep != "-") {
      if (ep.length() != 2) return false;
      if (!(ep[0] >= 'a' && ep[0] <= 'h')) return false;
      if (!((side == "w" && ep[1] == '6') || (side == "b" && ep[1] == '3')))
         return false;

   // The tricky part: The board.
   // Seven slashes?
   if (std::count(board.begin(), board.end(), '/') != 7) return false;
   // Only legal characters?
   for (int i = 0; i < board.length(); i++)
      if (!(board[i] == '/' || (board[i] >= '1' && board[i] <= '8')
            || piece_type_is_ok(piece_type_from_char(board[i]))))
         return false;
   // Exactly one king per side?
   if (std::count(board.begin(), board.end(), 'K') != 1) return false;
   if (std::count(board.begin(), board.end(), 'k') != 1) return false;
   // Other piece counts reasonable?
   size_t wp = std::count(board.begin(), board.end(), 'P'),
      bp = std::count(board.begin(), board.end(), 'p'),
      wn = std::count(board.begin(), board.end(), 'N'),
      bn = std::count(board.begin(), board.end(), 'n'),
      wb = std::count(board.begin(), board.end(), 'B'),
      bb = std::count(board.begin(), board.end(), 'b'),
      wr = std::count(board.begin(), board.end(), 'R'),
      br = std::count(board.begin(), board.end(), 'r'),
      wq = std::count(board.begin(), board.end(), 'Q'),
      bq = std::count(board.begin(), board.end(), 'q');
   if (wp > 8 || bp > 8 || wn > 10 || bn > 10 || wb > 10 || bb > 10
       || wr > 10 || br > 10 || wq > 9 || bq > 10
       || wp + wn + wb + wr + wq > 15 || bp + bn + bb + br + bq > 15)
      return false;

   // OK, looks close enough to a legal position. Let's try to parse
   // the FEN and see!
   Position p;
   p.from_fen(board + " " + side + " " + castleRights + " " + ep);
   return p.is_ok(true);
| improve this answer | |
  • 1
    It looks like all the actual validation is done in position.is_okay(). The code here just does a couple of basic checks to make sure it's formatted correctly and that it's worth doing the real validation (i.e. not obviously illegal). – undergroundmonorail Apr 14 '16 at 15:42

Here is a simple backtracking algorithm, provided that you have a function that can check reverse legal moves at every board state (also known as position):

function is_legal_state(state,move)

   //Terminate if a starting state was found. This immediately implies there
   //was a legal game that generated this state, in fact the backtracking
   //can tell you precisely such a game       
   if (state in starting board state)
     return true

   //Apply some move to get to a new state, state is a persistent object

   //Generate all legal "reverse" moves, that is, moves that could have
   //been performed to get to the current state from another position,
   //provided the previous position was valid. You do not have to check the
   //validness of the previous state, you just have to make sure the
   //transitioning move was valid
   legalmoves = enumerate_all_reverse_moves( state )

   for local_move in legalmoves:
     return is_legal_state(state,local_move)

   //Reverse the move that was previously applied so backtracking can
   //work properly 

   return false
| improve this answer | |

There is nothing in the FEN specification saying that the represented position must be reachable from the initial array. Proving that a given position is reachable from the initial array is an unsolved problem.

In a valid FEN string, the half move count must be in agreement with the en passant target square; if a target square is present, then the half move count must be zero. the half move count must also be in agreement with the full move number; e.g., a half move count of ten is incompatible with a full move number of three.

| improve this answer | |

Coming to the party late.

It’s not possible to 100% validate whether a position is legal, but why should validation matter? We can play chess whether or not the position happens to derive from the starting position (so-called “game array”). There might be a very interesting position to analyse but it just happens that it’s illegal.

So I would check just:

  • Is there exactly 1 king from each side?
  • Are there no pawns on the first or eighth rank?
  • Is the side to move not giving check already?

If that’s three YESes then we can play chess forwards from this diagram. And even this short list of conditions we might be able to loosen.

| improve this answer | |

Also , if the K is checked twice , the position must have resulted from a discovered check - Q and R,B or N , N and R or B , or R & B. Pawns are never involved in a discovered check.

| improve this answer | |
  • 2
    Pawns are never involved in a discovered check - not true. – Herb Oct 9 at 21:24
  • 1
    Q + Q is possible with pawn promotion – ajax333221 Oct 10 at 3:27

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.