Solving the technical interview / Logic programming

11 min read Original article ↗

by Samir Talwar

Saturday, 23 May 2026 at 09:00 CEST

With apologies to Aphyr.


You heave a sigh and get up from your desk, stopping by the kitchenette for a tea and a biscuit on the way to the meeting room. Recruiting for this role has not been going well, and your manager is starting to get nervous.

Nervous is bad. Nervous means more scrutiny, more questions, more micro-management. And nervous means you get nervous too. You can feel your heart beat faster as you walk over to the interview room. You can see a sillhouette through the frosted glass door; they must already be waiting for you.

You take a deep breath. The last few candidates have all been a little… odd. Let’s hope this one makes life a bit easier for you. You could use a win.

You stride in. “Um, hello.”

“Hello, dear! It’s lovely to meet you.”

You pause. Seated at the conference table is an older woman, full of smiles, unapologetically beaming at you, half a biscuit in her hand, a laptop in front of her. As she opens it, you notice that it seems to have seen better days; its keyboard is covered in cake crumbs, the trackpad has been worn smooth, the hinge creaks, and a fan roars to life.

You relax a little. Perhaps this won’t be like the one last month. Or the one last week.

“I’m ready when you are, sonny jim.”

You try not to appear taken aback by her familiarity, as you stammer out the start of the problem statement.

“Are you familiar with chess?”

She frowns at you. The light flickers. You meet her eyes, and are whisked away. A horse, rearing, its rider vaulting over a warrior with a pike. A man of God speaks, his voice booming, a tower crumbles. A queen stands over a king, with her dagger pointed at his neck, her face steeled even as tears form in her eyes.

Was it a moment, or a lifetime?

“Yes, I am. Intimately, you might say.” She offers you a wink. You’re not sure why.

“Well,” you stammer, “imagine a chessboard. The problem is called N-Queens. For a given chessboard of size N×N, you need to find a way to place N queens on that board, safely. Don’t worry about actually solving—”

“Safely, you say? Surely that depends on the queen?”

“Well… um. In this situation, imagine they all move like chess queens, and they want to capture each other. You need to find a way to place them all so none of them can reach another.”

“Yes, I’ve dealt with royalty before. Best to keep them in separate castles. But if you can’t, let’s at least be clever about the seating plan.”

She starts typing:

{-# LANGUAGE GHC2024 #-}
{-# LANGUAGE BlockArguments #-}
{-# OPTIONS_GHC -Wno-name-shadowing #-}

“Er… what’s this?”

“We want a fair game, don’t we? No arguments, no subterfuge.”

module Main where

import Control.Monad ((>=>))
import Data.Char qualified as Char
import Data.Maybe (listToMaybe)
import Data.List qualified as List
import Prelude hiding (negate)
import System.Environment (getArgs)

Suddenly, a memory, and not a positive one. “Hang on, is this Haskell? Um… I must warn you we haven’t had great experiences with all of that type-level trickery here.”

“Oh, very good. No, I don’t hold to that myself, either. You know where you are with variables, I always say. Types should be kept simple. Code is for humans to read, as they say!”

A weight on your chest starts to ease up. You hadn’t realised it was there.

“Ah, phew. Please, carry on.”

“Well, speaking of variables, let’s make some.”

newtype Var = Var Int
  deriving newtype (Eq, Enum)

data Term a
  = TAtom a
  | TVar Var
  deriving stock (Eq)

You start to interrupt, but she’s typing furiously now, and the clacking of the keyboard (why does it sound like a typewriter?) drowns out your question.

type Knowledge a = [(Var, Term a)]

She stops, for a moment. “A tree of knowledge. And one must climb it.”

climb :: Knowledge a -> Term a -> Term a
climb _ term@TAtom {} = term
climb knowledge (TVar v) =
  maybe (TVar v) (climb knowledge) $ lookup v knowledge

“Climb until the branch yields fruit. No more.”

“Er… how does this—”

Her eyes snap upwards, meeting yours. A woman, naked, reaches toward an apple, hanging from a tree. She takes it, bites it. Her eyes widen. Oblivion.

You stop talking.

“One cannot simply have knowledge, of course. We learn.”

type Lesson a = State a -> [State a]

data State a = State Var (Knowledge a)

emptyState :: State a
emptyState = State (Var 0) []

“Hang on, what’s a state?”

She smiles at you. “Haskell is a functional programming language. No mutation. When you learn, you are not the same person as before. The program isn’t either. To learn, it must transition, from state to state. You’re learning Haskell right now, aren’t you? And you won’t be the same person at the end of this conversation as you were at the beginning, let me tell you!”

You nod, your mouth shut. It seems safer.

“The first lesson is that if what we know is wrong, we know nothing.”

falsity :: Lesson a
falsity = mempty

“The second: even if we don’t learn anything new, we know what we know.”

truth :: Lesson a
truth = pure

“And of course, some lessons are not worth learning.”

negate :: Lesson a -> Lesson a
negate lesson state = case lesson state of
  [] -> pure state
  _ -> mempty

“I think I follow,” you manage to mumble. “If it teaches us something, it is false, and if it claims to be a dead end, we were right all along?”

She beams at you, and takes her hands from the keyboard, leaning down to rummage around in a huge handbag by her feet. After a brief moment, she produces a metal box, and opens it up.

“Very good, sonny. Would you like a piece of ginger cake? I baked it this morning.”

You take the piece of cake. She waits, expectantly. You take a bite.

It’s delicious.

“Now, my boy, let’s talk about what it means for things to be the same.”

Her fingers blur, faster than you thought possible. Cake crumbs fly through the air.

(===) :: forall a. (Eq a) => Term a -> Term a -> Lesson a
(x === y) (State vars knowledge) =
  maybe mempty (pure . State vars) $
    unifyTerm (climb knowledge x) (climb knowledge y)
  where
    unifyTerm (TAtom x') (TAtom y')
      | x' == y' = Just knowledge
      | otherwise = Nothing
    unifyTerm (TVar v) y' = unifyVar v y'
    unifyTerm x' (TVar v) = unifyVar v x'
    unifyVar v t
      | occurs v t = Nothing
      | otherwise = Just (pure (v, t) <> knowledge)
    occurs _ (TAtom _) = False
    occurs v (TVar tv) = tv == v

She cracks her knuckles. “My joints aren’t what they used to be, you know.

“Atoms are atoms; they’re the same or they ain’t, and if they ain’t… you’ve gone wrong. But variables… if you know that a variable is a term, then you’ve learned something new.”

“Unless it… occurs?”

“Yes, that’s right. If you don’t check what you already know, you’ll end up going in circles. And when you start thinking in circles, you’ll never stop.”

“Right, and—”

She cuts you off with a raised palm.

“Almost there. Now, where was I? Some lessons follow on from others, building on prior knowledge. Fortunately, Haskell gives us an operator for that.”

(&&&) :: Lesson a -> Lesson a -> Lesson a
(&&&) = (>=>)

“And if two things might be true, you can explore them one by one. So let’s stick them together.”

(|||) :: Lesson a -> Lesson a -> Lesson a
(|||) = (<>)

She grins. “Remember, a lesson returns a list. So we can have multiple possible truths.”

Your brow furrows. You remember why you’re here. “So about these queens…”

“Hush, sonny. Almost there. Now, we introduce a variable when we want to consider something new. And we make sure never to consider that variable twice; it’s gone, and its successor takes its place.”

consider :: (Term a -> Lesson a) -> Lesson a
consider f (State nextVar knowledge) =
  f (TVar nextVar) (State (succ nextVar) knowledge)

“And sometimes we need to check our understanding during a lesson.”

understand :: Term a -> (Term a -> Lesson a) -> Lesson a
understand term f state@(State _ knowledge) =
  f (climb knowledge term) state

“And of course, we would like to learn.”

learn :: Lesson a -> [Knowledge a]
learn lesson =
  map (\(State _ knowledge) -> knowledge) $ lesson emptyState

She leans back in her chair, satisfied.

“You see?”

“Well… not really. Where does it figure out where the queens go on a chessboard?”

“Oh, I see, you want the details. Alright, let’s go. I imagine we’ll need to add some numbers.”

add :: (Eq a, Num a) => Term a -> Term a -> Term a -> Lesson a
add x y z = understand x \x -> understand y \y -> understand z \z ->
  case (x, y, z) of
    (TAtom x, TAtom y, z) -> z === TAtom (x + y)
    (TAtom x, y, TAtom z) -> y === TAtom (z - x)
    (x, TAtom y, TAtom z) -> x === TAtom (z - y)
    _ -> falsity

A plus. Alright. This is unorthodox, but at least you understand the plus symbol. But a minus?

“What’s that subtraction doing there?”

“Addition, subtraction, what’s the difference?” She pauses, waiting for you to acknowledge her little joke, and you smile, weakly, your eyes telling a different story. She doesn’t notice, instead cracking her knuckles and starting to type again.

within :: (Eq a) => Term a -> [Term a] -> Lesson a
within x = foldr ((|||) . (=== x)) falsity

“We’re ready.”

queens :: Int -> Lesson Int
queens n = queens' n []
  where
    valid = map TAtom [0 .. pred n]
    queens' 0 _ = truth
    queens' m threatened = consider \rank -> consider \file -> consider \diag1 -> consider \diag2 ->
      (rank `within` valid)
        &&& negate (rank `within` threatenedRanks)
        &&& (file `within` valid)
        &&& negate (file `within` threatenedFiles)
        &&& add diag1 rank file
        &&& negate (diag1 `within` threatenedDiag1)
        &&& add rank file diag2
        &&& negate (diag2 `within` threatenedDiag2)
        &&& queens' (pred m) ((rank, file, diag1, diag2) : threatened)
      where
        (threatenedRanks, threatenedFiles, threatenedDiag1, threatenedDiag2) = List.unzip4 threatened

“Happy now?”

You furrow your brows, trying to process it. There’s a lot of ampersands, but they seem to do the right thing.

“And this… solves it, somehow?”

There’s no computation, apart from a couple of additions. And one of those might actually be a subtraction, who knows?

“Eat your cake, my boy. I can see you’re the sort who needs their hand held. You want to know where the queens are?”

data Position = Position Int Int
  deriving stock (Eq, Ord)

instance Show Position where
  show (Position rank file) =
    Char.chr (rank + Char.ord 'a') : show (file + 1)

You relax. Finally, something normal. Haskell’s a bit weird, but this code would be relatively at home in JavaScript too.

She pauses. “We’ll cheat a little. We know how our algorithm allocates variables, so let’s use that to get the rank and file.”

findQueens :: Knowledge Int -> Int -> [Position]
findQueens _ 0 = []
findQueens knowledge n =
  let offset = Var (fromIntegral (n - 1) * 4)
      TAtom rank = climb knowledge (TVar offset)
      TAtom file = climb knowledge (TVar (succ offset))
   in Position rank file : findQueens knowledge (n - 1)

“And then, we solve. Is just one answer alright?” She doesn’t wait for you to respond.

solveQueens :: Int -> [Position]
solveQueens n = List.sort (findQueens knowledge n)
  where
    knowledge = head $ learn (queens n)

main :: IO ()
main = do
  args <- getArgs
  let n = maybe 8 read $ listToMaybe args
  print $ solveQueens n

“Happy now, young lad?”

You realise that you never introduced yourself, she never asked your name. It doesn’t matter any more. You squeeze your eyes shut and take a breath, then open them.

“Can we run it?” you ask, tentatively.

She taps ghc -O2 -o interview Interview.hs && ./interview into a terminal. It chugs for a second, the laptop fans groaning, and it spits out:

[a1,b5,c8,d6,e3,f7,g2,h4]

“Does that look right to you, my boy?”

You nod, your heart beating faster and faster. You don’t remember what you said, but you must have wrapped it up, because the next thing you know, you are hovering over your desk, your still-full mug of tea in your hand, wondering what you did to deserve this.

You glance down. There’s an entire slice of cake waiting for you on your desk.

You sigh, sit down, and break off a piece.


  1. I relent
  2. Solving the technical interview
  3. Solving the technical interview, explained
  4. Monadic logic