RecallDeck
Interview track

Go Developer interview prep

A spaced-repetition deck of 278+ Go Developer interview questions — organised by topic and difficulty, and resurfaced right before you'd forget. Preview a few cards below, then sign in to study the whole track on an Anki-style SM-2 schedule.

278 cards14 topics

Free · sign in with GitHub · your progress stays yours.

What's covered

Every topic in this track, grouped the way you'd study it.

Databases

104 cards
SQL FundamentalsPostgres Internals & OptimizationNoSQL & Redis

DevOps & Infra

35 cards
Docker, CI/CD & Linux

System Design

29 cards
System Design

Behavioral

35 cards
Behavioral

Language Basics: Slices, Maps & Strings

15 cards
Slices, Maps & Strings

Interfaces & Type System

6 cards
Interfaces & Types

Goroutines, Channels & select

12 cards
Goroutines & Channels

sync, atomic & context

12 cards
sync & context

Scheduler, GC & Memory

8 cards
Runtime & Memory

Errors, defer & panic

7 cards
Errors & defer

Concurrency Patterns (Live Coding)

8 cards
Concurrency Patterns

Testing, Tooling & Generics

7 cards
Testing & Tooling

Sample questions

A few cards from the deck — reveal each answer, then sign in to study the full set on a schedule.

What does SELECT do, and in what order do the parts of a query execute?

Short answer: SELECT retrieves rows from tables. The logical execution order does NOT match the written order: first FROM, then WHERE, GROUP BY, HAVING, SELECT, DISTINCT, ORDER BY, LIMIT.

In depth:

Written (syntactic) order:

SELECT   DISTINCT col1, agg(col2)
FROM     t
WHERE    cond
GROUP BY col1
HAVING   agg_cond
ORDER BY col1
LIMIT    10 OFFSET 20;

Logical execution order:

1. FROM / JOIN      -- which tables, how to join
2. WHERE            -- row filter BEFORE grouping
3. GROUP BY         -- grouping
4. HAVING           -- group filter AFTER aggregation
5. SELECT           -- evaluate expressions, aliases
6. DISTINCT         -- remove duplicates
7. ORDER BY         -- sorting
8. LIMIT / OFFSET   -- slice

⚠️ Gotcha: an alias from SELECT cannot be used in WHERE (since WHERE runs before SELECT), but it can be used in ORDER BY and often in GROUP BY (depends on the DBMS). Example of the error:

SELECT salary * 12 AS annual FROM emp WHERE annual > 100000; -- ERROR
SELECT salary * 12 AS annual FROM emp ORDER BY annual;        -- OK

How does WHERE differ from ORDER BY, and how do LIMIT/OFFSET work?

Short answer: WHERE filters rows, ORDER BY sorts the result, LIMIT n OFFSET m returns n rows, skipping the first m.

In depth:

-- Pagination: page 3 with 20 records each (skip 40, take 20)
SELECT id, name
FROM users
WHERE active = TRUE
ORDER BY created_at DESC
LIMIT 20 OFFSET 40;

⚠️ Gotcha: LIMIT without ORDER BY returns a NON-deterministic set of rows — the order isn't guaranteed. Also, OFFSET is slow at large values (the DBMS still reads and discards the skipped rows) — for deep pagination use keyset pagination:

-- keyset: faster than a large OFFSET
SELECT * FROM users WHERE id > :last_seen_id ORDER BY id LIMIT 20;

What does DISTINCT do, and what's the catch?

Short answer: DISTINCT removes duplicate rows from the result.

In depth:

SELECT DISTINCT department FROM employees;        -- unique departments
SELECT DISTINCT department, city FROM employees;  -- unique PAIRS (department, city)

⚠️ Gotcha: DISTINCT applies to ALL columns in the SELECT, not to a single one. SELECT DISTINCT a, b ≠ "unique a". Also, COUNT(DISTINCT col) counts unique values, ignoring NULL.

What kinds of JOIN exist, and how do they differ?

Short answer: INNER — matches only; LEFT — all left rows + matches on the right (otherwise NULL); RIGHT — the mirror of LEFT; FULL — everything from both sides; CROSS — Cartesian product; SELF — a table joined to itself.

In depth:

Suppose we have:

employees           departments
+----+--------+----+   +----+----------+
| id | name   |dept|   | id | title    |
+----+--------+----+   +----+----------+
| 1  | Anna   | 10 |   | 10 | IT       |
| 2  | Boris  | 20 |   | 30 | Finance  |
| 3  | Vera   | NULL|  +----+----------+
+----+--------+----+

INNER JOIN — the intersection (∩). Only rows where there's a match in BOTH tables:

SELECT e.name, d.title
FROM employees e
INNER JOIN departments d ON e.dept = d.id;
-- Anna | IT      (Boris dropped: dept 20 isn't in departments;
--                 Vera dropped: dept = NULL)
employees    departments
   [ ##### ]              <- only the intersection

LEFT JOIN — all rows of the left table + matches on the right (no match -> NULL):

SELECT e.name, d.title
FROM employees e
LEFT JOIN departments d ON e.dept = d.id;
-- Anna  | IT
-- Boris | NULL   <- kept, no department
-- Vera  | NULL   <- kept, dept = NULL
[ left ##### ]   <- all of the left + the intersection

RIGHT JOIN — the mirror of LEFT (all rows of the right):

SELECT e.name, d.title
FROM employees e
RIGHT JOIN departments d ON e.dept = d.id;
-- Anna | IT
-- NULL | Finance  <- department 30 has no employees

FULL OUTER JOIN — all rows of both tables:

SELECT e.name, d.title
FROM employees e
FULL OUTER JOIN departments d ON e.dept = d.id;
-- Anna  | IT
-- Boris | NULL
-- Vera  | NULL
-- NULL  | Finance

CROSS JOIN — the Cartesian product (each with each), with no condition:

SELECT e.name, d.title FROM employees e CROSS JOIN departments d;
-- 3 rows × 2 rows = 6 rows

SELF JOIN — a table to itself (e.g., employee -> manager):

SELECT e.name AS employee, m.name AS manager
FROM employees e
LEFT JOIN employees m ON e.manager_id = m.id;

💡 How LEFT differs from INNER, by example: if there's no match on the right, INNER DROPS the row, while LEFT KEEPS it, substituting NULL into the right-hand columns. That's why LEFT JOIN is often used to find "orphaned" records:

-- Employees without a department
SELECT e.name
FROM employees e
LEFT JOIN departments d ON e.dept = d.id
WHERE d.id IS NULL;

⚠️ Gotcha: a condition on the right table in a LEFT JOIN must go in ON, not in WHERE, otherwise LEFT turns into INNER:

-- WRONG: the filter in WHERE cuts off rows with NULL on the right -> behaves like INNER
SELECT e.name, d.title FROM employees e
LEFT JOIN departments d ON e.dept = d.id
WHERE d.title = 'IT';

-- RIGHT: if you need to keep all rows on the left
SELECT e.name, d.title FROM employees e
LEFT JOIN departments d ON e.dept = d.id AND d.title = 'IT';

What is GROUP BY and aggregate functions?

Short answer: GROUP BY groups rows by column values; aggregates (COUNT, SUM, AVG, MIN, MAX) compute one value per group.

In depth:

SELECT department,
       COUNT(*)        AS headcount,
       SUM(salary)     AS payroll,
       AVG(salary)     AS avg_salary,
       MIN(salary)     AS min_salary,
       MAX(salary)     AS max_salary
FROM employees
GROUP BY department;

⚠️ Gotcha: in standard SQL, the SELECT can only use columns from GROUP BY or aggregates. SELECT name, dept, COUNT(*) ... GROUP BY dept is an error (name is neither grouped nor aggregated). PostgreSQL forbids it; MySQL (in non-strict mode) silently returns an arbitrary name.

COUNT(*) vs COUNT(col) vs COUNT(DISTINCT col) — what's the difference?

Short answer: COUNT(*) — all rows; COUNT(col) — rows where col is NOT NULL; COUNT(DISTINCT col) — unique non-NULL values.

In depth:

-- Table: 5 rows, 2 NULLs in phone, 1 duplicate
SELECT
  COUNT(*)               AS total,        -- 5
  COUNT(phone)           AS with_phone,   -- 3 (NULLs not counted)
  COUNT(DISTINCT phone)  AS unique_phones -- 2
FROM users;

⚠️ Gotcha: COUNT(col) silently ignores NULL — a common cause of discrepancies in reports. If you need to count all rows, always use COUNT(*).

Ready to make it stick?

Start your first session in under a minute. Your future self, mid-interview, will thank you.

Other interview tracks

RecallDeckSpaced-repetition interview prep