Minimum steps to reach target by a Knight In Scala
At first glance, the isValidPos() method looks to be clear and succinct, requiring only two tests to see if the passed position is on the board.

def isValidPos(position: Pos): Boolean =
  ((0 until Size) contains position.row) &amp;&amp; ((0 until Size) contains position.col)


But it turns out that the contains() method on a Range is a bit complicated and most valid positions will have to pass around 10 if checks before the method returns true, so there are probably more efficient ways to get the same result.

But a bigger question is, can this be done without any mutable collections or variables?

One approach is to create a Knight class that holds all current valid board positions and the history of how each position was arrived at and then, when taking the next step, instead of modifying the Knight instance you create a new immutable Knight instance with all the new information built from the current (old) data.

type Cell = (Int,Int)

class Knight(dim :Int, history :Map[Cell,Cell], val cells :Set[Cell]) {
  def this(dim :Int, cell :Cell) = this(dim, Map(cell-&gt;(-1,-1)), Set(cell))

  def backtrack(cell :Cell) :List[Cell] =
    if (history(cell) == (-1,-1)) cell :: Nil
    else                          cell :: backtrack(history(cell))

  def next :Knight = (new Knight(dim, _:Map[Cell,Cell], _:Set[Cell])).tupled{
    cells.foldLeft((history, Set.empty[Cell])){
      case ((hMap,cSet),(x,y)) =&gt;
        val newSet = Set((x-2, y-1), (x-2, y+1), (x-1, y-2), (x-1, y+2)
                        ,(x+1, y-2), (x+1, y+2), (x+2, y-1), (x+2, y+1)
                        ).filter{ case (a,b) =&gt;
              a &gt;= 0 &amp;&amp; a &lt; dim &amp;&amp; b &gt;= 0 &amp;&amp; b &lt; dim &amp;&amp; !hMap.keySet((a,b))
                                }
        (hMap ++ newSet.map(_ -&gt; (x,y)).toMap, cSet ++ newSet)
    }
  }
}

def knightMoves(boardSize :Int, knight :Cell, target :Cell) :List[Cell] = {
  def loop(k1 :Knight, k2 :Knight) :List[Cell] = {
    val met :Set[Cell] = k1.cells.intersect(k2.cells)
    if (met.nonEmpty) k1.backtrack(met.head).reverse ++
                      k2.backtrack(met.head).tail
    else if (k1.cells.isEmpty || k2.cells.isEmpty) Nil
    else if (k1.cells.size &gt; k2.cells.size) loop(k1, k2.next)
    else                                    loop(k1.next, k2)
  }
  loop(new Knight(boardSize, knight), new Knight(boardSize, target))
}

knightMoves(3,(0,1),(1,1))    //res0: List[Cell] = List()
knightMoves(14,(0,1),(10,12)) //res1: List[Cell] = List((0,1), (2,2), (4,3), (6,4), (7,6), (8,8), (9,10), (10,12))


Some efficiency is obtained here by making the target position a Knight instance as well. That way they can "move" toward each other and, as soon as they intersect, a path from one to the other is found.