Part 01
About you
Fields marked * are required. Your answers save in this browser as you go, so you can come back to them.
Continue to Part 02 →
Part 02
Craft
Twelve quick questions on web security, data and the front end, then three short written answers. Answer from your own experience — we read the written answers carefully, and we are looking for your thinking, not a textbook answer.
Quick questions
01 What reliably prevents SQL injection?
Escaping quotes with a regular expression
Parameterised queries (prepared statements)
Validating input length
Base64-encoding user input before the query
02 How should user passwords be stored?
An MD5 hash
A SHA-256 hash
Encrypted with AES so support can recover them
A slow, salted password hash such as bcrypt or Argon2
03 A signed-in user asks for a resource they are not allowed to see. Which status code fits best?
400 Bad Request
401 Unauthorized
403 Forbidden
500 Internal Server Error
04 Which HTTP method is idempotent but not safe?
GET
POST
PUT
PATCH
05 Users can write comments that appear on a public page. What is the main defence against XSS?
Validate the comment when it is submitted, then render it as-is
Escape the output for its context (HTML, attribute, URL, JS) every time you render it, ideally backed by a Content Security Policy
Store comments in an HttpOnly cookie
Only allow comments from signed-in users
06 The front end at app.example.com gets a CORS error calling api.example.com. What is the correct fix?
Add mode: "no-cors" to the fetch call
Have the API send Access-Control-Allow-Origin (and related headers) for https://app.example.com
Ask users to install a browser extension that disables CORS
Serve the API over plain HTTP
07 A page lists 50 orders, then runs one query per order to load its customer. What is this, and what fixes it?
A race condition — add a lock
The N+1 query problem — load the customers in one query with a JOIN, or with WHERE id IN (…) / eager loading
A memory leak — restart the server more often
A missing index — add an index on the orders table and keep the loop
08 Where is a session token hardest to steal through an XSS bug?
localStorage
sessionStorage
A cookie flagged HttpOnly, Secure and SameSite
A global JavaScript variable
09 Your app has users in Cairo, Istanbul and Dubai. How should it store timestamps?
In each user's local time
In the server's local time
In UTC, converting to the viewer's time zone when displaying
As formatted strings like "Tue 3 PM"
10 In React, you render a list that users can reorder and delete from. What should each item's key be?
The array index
Math.random()
A stable, unique ID from the data
Keys are optional
11 What does Largest Contentful Paint (LCP) measure?
The total size of the page in bytes
How long until the largest image or text block in the viewport is rendered
How long the server takes to respond
How much the layout shifts while loading
12 You realise you pushed a live API key to a shared Git repository an hour ago. What do you do first?
Delete the commit with a force-push
Make the repository private
Revoke and rotate the key, then remove it from the code and history
Add the file to .gitignore
Short answers
Tell us about one system you built end to end. What was the hardest trade-off you made, and would you make it again?
0 / 200 words
A client's Shopify or WordPress site takes 8 seconds to load on mobile. Walk us through how you find the cause before you change anything.
0 / 150 words
How do you use AI coding tools (Copilot, Claude, Cursor or similar) — and where do you refuse to trust them?
0 / 150 words
← Back Continue to Part 03 →
Part 03
Code review
A junior developer opened this pull request: "Add login and lead export for the admin." It is Node.js and Express with MySQL, but you do not need to know Express to review it. Read it as if it would go live tomorrow.
1 // routes/leads.js
2 const express = require('express');
3 const mysql = require('mysql2');
4 const router = express.Router();
5 const db = mysql.createConnection({ host: 'db', user: 'root', password: 'Tee8Admin2024!', database: 'crm' });
6
7 router.post('/login', (req, res) => {
8 const { email, password } = req.body;
9 db.query(`SELECT * FROM users WHERE email = '${email}' AND password = '${password}'`, (err, rows) => {
10 if (rows.length > 0) {
11 res.cookie('user', JSON.stringify(rows[0]));
12 res.json({ ok: true, user: rows[0] });
13 } else {
14 res.status(200).json({ ok: false, error: 'Wrong password for ' + email });
15 }
16 });
17 });
18
19 router.get('/leads/export', async (req, res) => {
20 const user = JSON.parse(req.cookies.user);
21 if (user.role = 'admin') {
22 const leads = db.query('SELECT * FROM leads');
23 for (const lead of leads) {
24 lead.owner = await db.promise().query('SELECT * FROM users WHERE id = ' + lead.owner_id);
25 }
26 res.send(leads.map(l => `${l.name},${l.email},${l.phone},${l.notes}`).join('\n'));
27 }
28 });
29
30 router.delete('/leads/:id', (req, res) => {
31 db.query('DELETE FROM leads WHERE id = ' + req.params.id);
32 res.json({ deleted: true });
33 });
34
35 module.exports = router;
List every problem you find, most serious first. Give line numbers.
0 / 400 words
Rewrite the login route the way you would merge it. Any language or framework is fine.
0 / 400 words
Write the first comment you would leave on the PR for the junior. Keep it to what you would really say.
0 / 150 words
I confirm these answers are my own, and I agree that TEE8 may store my details and work to assess this application. TEE8 deletes the data of candidates it does not hire within 12 months, or sooner on request.
← Back
Submit Parts 01–03 and release the brief →
Submitting starts your 72-hour clock for Part 04. If now is not a good time, come back when you can give the brief proper attention — your answers stay saved in this browser.