MAD Study Hub

Home / MAD1 / Week 8

Week 8 · The frontend: JavaScript, the DOM and fetch

Official syllabus line: Application Frontend
About 100 minQuiz 2 material (see exam radar)BSCS2003 (+ project BSCS2003P) · Thejesh G N, Nitin Chandrachoodan
Before you start: MAD1 weeks 1-6 (HTML, CSS, Flask routes and JSON APIs); week 7 helps for caching. A little programming (variables, loops, functions).

The one-minute story

Think of it like this

A restaurant with a waiter who thinks. Until now your Flask app was a kitchen that sent out a whole new menu (a full HTML page) every time anything changed. JavaScript puts a clever waiter at the table (inside the browser). When you tick "no onions", the waiter crosses the word out on your own card (the DOM) without walking back to the kitchen. When something really needs the kitchen (save my order), the waiter phones it with a short note in a standard form (fetch + JSON), listens to the reply, and updates one line on your card.

HTML is the content, CSS is the look, JavaScript is the behaviour. The kitchen (Flask) stays in charge of truth and security; the waiter only makes the experience smooth.

By the end of this week you can:

  • add JavaScript to a page correctly (script, defer, external files);
  • write variables, functions, arrays and objects in JavaScript and predict their results;
  • select and change the DOM (getElementById, querySelector, textContent, innerHTML, createElement, classList, style);
  • handle events and read forms with JavaScript;
  • call a Flask JSON API with fetch (GET and POST), handle status codes and errors;
  • explain client vs server rendering, localStorage vs cookies, CORS, XSS risk of innerHTML, and what Vue is for.

Words you need

Words you need
  • JavaScript (JS) - the programming language that runs inside the browser and can change the page.
  • DOM (Document Object Model) - the browser's live tree of objects built from your HTML; JS reads and changes the DOM.
  • Event - something that happens: a click, a key press, a form submit, the page finishing loading.
  • Event listener - a function that runs when a given event happens on an element.
  • JSON - a text format for data: objects, arrays, strings, numbers, true/false/null.
  • AJAX - updating part of a page by talking to the server in the background, without a full reload.
  • Fetch API - the modern browser function fetch(url, options) that makes HTTP requests and returns a Promise.
  • Promise / async / await - a way to write code that waits for a slow thing (network) without freezing the page.
  • Client-side rendering - the browser builds the HTML from data using JS.
  • Server-side rendering - the server builds the HTML (Jinja) and sends it ready.
  • localStorage - small key-value storage kept in the browser, never sent to the server automatically.
  • CORS - the browser rule that blocks a page from reading responses from a different origin unless that server allows it.
  • XSS (cross-site scripting) - an attacker's script running inside your page.
  • Framework - a library that structures a big frontend (Vue, React, Angular).

Idea 1: JavaScript in HTML, and a JS refresher

Put JS in a script tag, inline or in an external file. Where the tag sits and defer matter because JS that touches an element must run after the element exists.

<!DOCTYPE html>
<html>
<head>
  <title>Hello JS</title>
  <script src="app.js" defer></script>   <!-- external, runs after the HTML is parsed -->
</head>
<body>
  <h1 id="title">Hi</h1>
  <script>
    console.log("inline script, h1 exists:", document.getElementById("title") !== null);
  </script>
</body>
</html>
AttributeBehaviour
nonedownload and run immediately, blocks the parsing
deferdownload in parallel, run after the whole HTML is parsed, in order
asyncdownload in parallel, run as soon as ready, order not guaranteed

Classic rule: a script in head without defer runs before the body exists, so document.getElementById("title") is null. Fix: put the script at the end of body, or use defer, or wait for the DOMContentLoaded event. In Flask, JS files go in static/ and are linked with url_for('static', filename='app.js').

console.log() prints to the browser's console (developer tools, key F12). It is not alert() (a popup) nor document.write() (writes into the page).

A refresher on the language

let n = 5;            // can be reassigned
const name = "Asha";  // cannot be reassigned
var old = 1;          // old style; avoid

function square(x) { return x * x; }
const cube = x => x * x * x;          // arrow function

const marks = [40, 90, 65];           // array
marks.push(70);                       // add at end
console.log(marks.length, marks[0]);  // 4 40
console.log(marks.map(m => m + 5));        // [45, 95, 70, 75]
console.log(marks.filter(m => m >= 65));   // [90, 65, 70]

const user = { name: "Ravi", city: "Pune" };   // object
console.log(user.name, user["city"]);          // Ravi Pune
const { name: nm } = user;                     // destructuring
console.log(`Hello ${user.name}`);             // template string

Surprises that exams love (all checked in Node):

console.log("5" + 3);     // "53"  (+ with a string joins)
console.log("5" - 3);     // 2     (- converts to numbers)
console.log(5 == "5");    // true  (loose equality converts)
console.log(5 === "5");   // false (strict: also compares type)
console.log(typeof null); // "object" (an old quirk)
console.log([10, 9, 1].sort());  // [1, 10, 9]  (sorts as text!)
console.log(0.1 + 0.2);   // 0.30000000000000004

let is block-scoped; var is not: three callbacks made in a var loop all see the final i ([3,3,3]), but with let they see [0,1,2]. Always use ===, and let/const.

Idea 2: The DOM

The browser turns your HTML into a tree of objects. JS changes the tree; the screen updates. Changing the DOM does not change your HTML file and the DOM is not identical to the source: it is built before your JS runs and JS can change it afterwards. (Both were in a real paper. JS is the usual way to use the DOM, but it is a language-neutral interface: "DOM can only be manipulated with JS" is false, and "its structure is rigid" is false.)

The idea

Pick an element, then change it.

TaskCode
by iddocument.getElementById("msg")
first match of a CSS selectordocument.querySelector("#msg"), ".card", "ul li"
all matchesdocument.querySelectorAll("li")
all by tagdocument.getElementsByTagName("li") (a live collection)
change text safelyel.textContent = "Hi" (or el.innerText)
change HTML (risky)el.innerHTML = "<b>Hi</b>"
form valueinput.value
styleel.style.fontWeight = "bold" (camelCase)
classesel.classList.add("hot"), remove, toggle, contains
attributeel.setAttribute("href", "/x")
create / insert / removedocument.createElement("li"), parent.appendChild(li), li.remove()

querySelector returns the first match, or null if none. querySelectorAll returns all matches.

Solved example: 1. Making text bold (a real question, three times)

Question. A page has <input id="txt1" value="Welcome..."> and a button calling doSomething(). Which code makes the text in the box bold?

function doSomething(){
  const txt1 = document.getElementById("txt1");   // the element
  txt1.style.fontWeight = "bold";                 // style is an OBJECT; fontWeight is camelCase
}

Solution. Correct: document.getElementById("txt1").style.fontWeight = "bold" (with or without a temporary variable). Wrong: .style = "bold" (we tried it: it sets an invalid style string and nothing becomes bold), and getElementById("txt1").value followed by .style... because .value is a string, and a string has no style (it throws TypeError). CSS font-weight becomes JS fontWeight; background-color becomes backgroundColor.

Solved example: 2. Adding to a list

Question. The page has <input id="txt_course">, a button "Add" and an empty <ul id="courses">. Complete add_item() so each typed course appears as a list item.

function add_item(){
  const course = document.getElementById("txt_course").value;
  document.getElementById("txt_course").value = "";
  const courses = document.getElementById("courses");      // the UL, not the input
  courses.innerHTML = courses.innerHTML + "<li>" + course + "</li>";
}

Solution. Take the input's value, then append "<li>...</li>" to the ul (getElementById("courses")). Using the input element as courses is the trap; assigning with = instead of += would replace the earlier items. After adding "Java" and "PDSA" the ul holds <li>Java</li><li>PDSA</li>. A cleaner way:

const li = document.createElement("li");
li.textContent = course;
courses.appendChild(li);
Solved example: 3. createElement builds a ROW

Question. What does this do when the button is clicked?

function perform(){
  let row = document.createElement("tr");
  let td1 = document.createElement("td"); td1.innerText = "name_1";
  let td2 = document.createElement("td"); td2.innerText = "city_1";
  row.appendChild(td1); row.appendChild(td2);
  document.getElementById("rows").appendChild(row);
}

Solution. It adds one new row (tr) to the element with id rows (the table body), with name_1 in the first cell and city_1 in the second. Order of appendChild is the order of the columns. Each click adds another row.

Solved example: 4. A live collection

Question. <ul id="lst"><li>App Dev-1</li></ul>. A script does let items = document.getElementsByTagName("li"); then document.getElementById("lst").innerHTML += "<li>App Dev-2</li><li>App dev projects</li>"; then console.log(items.length). Output?

Solution. 3. getElementsByTagName returns a live collection: it updates when the DOM changes, so it now sees all three li. (querySelectorAll returns a static list; take its length before and after if you want to see the difference.) We ran this in a DOM simulator and got 3.

CSS reminders the papers use with the DOM

Many rendering questions mix JS with CSS display. Quick table:

displayBehaviour
block (div, p, h1)starts on a new line, takes the full width
inline (span, a)flows within the text, width and height ignored
inline-blockflows in a line and accepts width/height; elements sit side by side until the line is full
flex (on the parent)children sit in a row by default, even if they are divs

So a div made inline-block appears on the same line as the next, and a flex container shows 1 2 3 as three boxes in a row. An id rule (#id1) beats a class rule (.class1), which beats a tag rule (span). Also: ol is numbered, ul is bulleted, and a nested list has its own numbering.

Watch out - real exam trap
  • .style = "bold" and .value.style do nothing useful; use .style.fontWeight.
  • innerHTML += re-parses everything and drops listeners on the old children.
  • getElementById returns one element (null if absent); getElementsByTagName returns a collection, so you index it ([0]).
  • Two ids in an HTML page must not clash, and the id in JS has no #, while querySelector needs #.

Idea 3: Events, forms and validation

An event listener runs your function when something happens. Three ways, all legal (a real question's answer was "All of these"):

<button id="myBtn">Click Me</button>
<button onclick="alert('Clicked!')">Click Me 2</button>
<script>
  document.getElementById("myBtn").onclick = function(){ alert("Clicked!"); };
  document.getElementById("myBtn").addEventListener("click", function(){ alert("Clicked!"); });
</script>

addEventListener is preferred because you can attach many listeners to one event and remove them. The listener gets an event object (e.target, e.preventDefault()).

document.getElementById("btn").addEventListener("click", () => {
  document.querySelector("#msg").textContent = "Welcome to JavaScript!";
});

Both getElementById and querySelector("#msg") work, and both textContent and innerHTML change the visible text. But msg.text = "..." does nothing visible (there is no .text property on a div), and it relies on the id as a global name.

Forms. Read input.value; stop the normal submit with e.preventDefault() when JS will handle it.

<form id="f">
  <input id="title" required minlength="3">
  <button>Add</button>
</form>
<script>
  document.getElementById("f").addEventListener("submit", e => {
    e.preventDefault();                       // do not reload the page
    const t = document.getElementById("title").value.trim();
    console.log("will send:", t);
  });
</script>

Validation: frontend vs backend. HTML5 attributes (required, minlength, type="email", pattern) and a JS function such as validateForm() give instant feedback in the browser (this is client-side validation; the browser's prompt on an empty required field reads "Please fill out this field." and the JS function does not run on the server). But anyone can bypass the browser (curl, dev tools), so the backend must validate again; frontend validation is for the user's convenience, never for security. The CSS pseudo classes :valid and :invalid can colour a field as the user types (an input with a minlength turns from red to green once the minimum length is reached and stays green while typing is still allowed).

A form with no method sends GET: the data goes in the URL. method="post" puts it in the body.

Idea 4: JSON and the Fetch API (AJAX)

JSON looks like a JS object but is text: keys in double quotes, no trailing commas, no comments, values only string, number, boolean, null, array, object.

const txt = '{"name":"Balu","age":30,"city":"New Delhi"}';
const obj = JSON.parse(txt);          // text -> object
console.log(obj.age * 3);             // 90
console.log(JSON.stringify(obj));     // object -> text: {"name":"Balu","age":30,"city":"New Delhi"}

A real paper put this in innerHTML as "Hi\n" + obj.name + ",\t" + obj.city + "," + obj.age*3. The page shows Hi Balu, New Delhi,90: the string really contains a newline and a tab, but HTML collapses whitespace to one space, and age*3 is a number 90 (not "303"). Notes: JSON.stringify drops undefined values ({a:1,c:undefined} becomes {"a":1}); JSON.parse on bad JSON such as {'a':1} throws SyntaxError.

fetch with a Flask API

Our Flask API (we ran it):

@app.get("/api/items")
def lst(): return jsonify(items)

@app.post("/api/items")
def add():
    data = request.get_json(silent=True)
    if not data or "title" not in data:
        return jsonify(error="title required"), 400
    it = {"id": len(items) + 1, "title": data["title"]}
    items.append(it)
    return jsonify(it), 201

@app.get("/api/items/<int:i>")
def one(i): ...                       # returns {"error": "not found"}, 404 if missing

The browser side, with async/await (the modern style):

async function loadItems() {
  const res = await fetch("/api/items");          // GET by default
  if (!res.ok) { throw new Error("HTTP " + res.status); }
  const items = await res.json();                  // parse the body
  const ul = document.getElementById("list");
  ul.innerHTML = "";
  for (const it of items) {
    const li = document.createElement("li");
    li.textContent = it.title;                     // textContent: safe
    ul.appendChild(li);
  }
}

async function addItem(title) {
  const res = await fetch("/api/items", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ title: title })
  });
  const data = await res.json();
  if (res.status === 201) { loadItems(); } else { alert(data.error); }
}

Same idea with .then: fetch(url).then(r => r.json()).then(data => ...).catch(err => ...).

Results from our test run:

Callres.statusres.ok
GET /api/items200true
POST with JSON header and body201true
GET /api/items/99404false, but fetch did not throw
POST without Content-Type: application/json400false (Flask's get_json(silent=True) gave nothing)
server not runningnonefetch throws TypeError (network error)
The idea

The two kinds of failure. fetch rejects (goes to catch) only on a network failure. A 404 or 500 is a successful HTTP exchange, so you must check res.ok (true for 200-299) or res.status yourself. For POST JSON you need both the Content-Type header and JSON.stringify on the body. await res.json() is also asynchronous.

AJAX is this idea: a page that updates part of itself with background requests, without reloading. Debugging: in the browser's dev tools use the Console (errors and console.log), Network (see each request, its status, headers and response body) and Elements (the live DOM). console.log(res.status) is your friend.

In your project

Add one fetch call to your project: for example a "mark as done" button that does PUT /api/tasks/3 and updates the row without reload. In the viva be ready to say what happens when the API returns 401, 404 or 500 and show where you check res.ok.

Idea 5: Where is the page built? Rendering styles and frameworks

Server-side rendering (SSR, Jinja)Client-side rendering (CSR, JS + API)
Server sendsfinished HTMLan almost empty page, JS, and later JSON
First paintfast, works without JSslower first load, then very smooth
Next pagefull reloadonly the changed part
SEO, simple siteseasyharder
FitsMAD1 style appsMAD2 style apps with a JSON API

Progressive enhancement: build a page that works with plain HTML and forms (SSR), then add JS on top for comfort. If JS fails, the site still works. This is a good, safe default design.

Frameworks (Vue, React, Angular) give structure for large UIs: components, reactive data (change the data and the screen updates automatically), and routing. They are frontend frameworks. Node.js is not a frontend framework: it runs JS on the server. WebAssembly is a binary format, not a web component; the real web-component standards are Custom Elements, Shadow DOM and HTML Templates. You will use Vue in MAD2; for MAD1 you only need "why use one": less manual DOM code. JS is not always the core of a frontend; HTML and CSS are the core and JS adds extra functionality.

Idea 6: Storing things in the browser, CORS and XSS

localStoragecookies
Sizeabout 5 MBabout 4 KB
Sent to server?no, JS onlyyes, automatically with every request to that site
Expirynever, until clearedset by Expires/Max-Age, or when the session ends
Typical useUI preferences, a draftsession id, "remember me"
localStorage.setItem("theme", "dark");
console.log(localStorage.getItem("theme"));   // "dark"
localStorage.removeItem("theme");
localStorage.setItem("user", JSON.stringify({name: "Asha"}));   // store objects as text

localStorage holds strings only, hence JSON.stringify. Anything JS can read, an attacker script can read too, so do not keep secrets there.

CORS in one paragraph. The browser enforces the same-origin policy: a page from http://a.com may not read a response from http://b.com (a different scheme, host or port) unless server b answers with Access-Control-Allow-Origin: http://a.com. In Flask-CORS you write CORS(app). If your page and API are served by the same Flask app, there is no CORS issue. CORS is enforced by the browser, not by the server, so curl is not affected.

XSS and innerHTML. If you put user-supplied text into innerHTML, the browser parses it as HTML, so a comment containing an img tag with an onerror handler would run an attacker's script in your visitors' browsers. Checked in a DOM simulator:

el.textContent = '<img src=x onerror="alert(1)">';  // shows the text literally: &lt;img ...&gt;
el.innerHTML   = '<img src=x onerror="alert(1)">';  // creates a real img element (dangerous)

Rule: textContent for plain text; innerHTML only for HTML you wrote yourself. More about XSS and its server-side twin in week 9.

Your turn

Your turn: 1. Predict the JS

What do these print? console.log("2" + 2), console.log("2" * "3"), console.log([3, 20, 100].sort()), console.log(typeof []).

Need a hint?

+ with a string joins. * converts. sort() with no function compares as text.

Show the full solution

"22", 6, [100, 20, 3] (as text order "100" < "20" < "3"), "object".

Your turn: 2. Change the DOM

<p id="info" class="note">Hello</p>. Write JS that (a) changes the text to Welcome, (b) adds the class hot, (c) makes the colour red.

Need a hint?

textContent, classList, style.

Show the full solution
const p = document.getElementById("info");
p.textContent = "Welcome";
p.classList.add("hot");
p.style.color = "red";
Your turn: 3. Spot the bug

<ul id="todo"></ul>. The code document.getElementById("todo").innerHTML = "<li>" + text + "</li>" is called for every new task, but only the last task is visible. Why, and fix it.

Need a hint?

= or +=?

Show the full solution

= replaces all earlier content. Use +=, or better appendChild of a created li.

Your turn: 4. fetch and status codes

Write a function that POSTs {"title": t} to /api/items and shows error from the response if the status is not 201.

Need a hint?

Header, JSON.stringify, then check res.status.

Show the full solution
async function add(t){
  const res = await fetch("/api/items", {method:"POST",
      headers:{"Content-Type":"application/json"}, body: JSON.stringify({title:t})});
  const data = await res.json();
  if (res.status !== 201) alert(data.error);
}
Your turn: 5. Safe or not

A comment box shows what users type with div.innerHTML = comment. Why is this dangerous and what is the fix?

Need a hint?

Think of what text an attacker would type.

Show the full solution

They can type HTML with a script handler, which then runs in every visitor's browser (XSS). Fix: div.textContent = comment, and also escape or validate on the server.

Exam radar

Exam radar

About 16 of ~692 Quiz 1 questions, about 5 of ~624 Quiz 2 questions and about 47 of ~1600 End Term questions match this week's keywords (rough keyword counts). The real questions are far more about the DOM and small JS snippets than about fetch. The sample of about 26 questions repeats:

  • Make text bold with JS: asked in three different End Terms (answer: getElementById("txt1").style.fontWeight = "bold"), also the variant with .value (wrong).
  • Add to a list or table dynamically: innerHTML on the correct element (the ul), and createElement("tr") with two td cells.
  • Event listeners: onclick vs addEventListener, "All of these".
  • True/false about the DOM: it is created before the JS runs; JS can change it; changing it does not change the HTML file; HTML and DOM are not identical.
  • console.log vs alert vs document.write; JSON.parse with arithmetic (age*3 = 90).
  • Frontend frameworks (Node JS is not one), web components, frontend vs backend validation, HTML vs CSS vs JS roles.
  • Rendering HTML+CSS (inline-block, flex row, specificity).
Watch out - real exam trap
  • style = "bold", .value.style, appending to the wrong element, = instead of +=.
  • getElementsByTagName is live; querySelectorAll is not.
  • Frontend validation is not security; the backend must repeat the checks.
  • HTML collapses whitespace: \n and \t show as one space in a page.
  • fetch does not throw on 404; check res.ok.

Quick quiz

Quick quiz

Which code bolds the text of the element with id txt1?

  • document.getElementById("txt1").style = "bold";
  • document.getElementById("txt1").style.fontWeight = "bold";
  • document.getElementById("txt1").value.style.fontWeight = "bold";
  • document.getElementsByName("txt1").style.fontWeight = "bold";
The style property is an object; CSS font-weight is fontWeight. .value is a string with no style.

Select all TRUE statements about the DOM. (select all that apply)

  • JS can interact with DOM elements.
  • The DOM is created before the JS runs.
  • Changing the DOM changes the HTML source file.
  • HTML and the DOM are identical.
The DOM is a live in-memory copy; editing it never rewrites the file.

const obj = JSON.parse('{"name":"Balu","age":30}'); console.log(obj.age * 3); prints...

  • 303
  • NaN
  • 90
  • 30
age is a number, so multiplication gives 90.

Which is NOT a frontend framework?

  • Vue JS
  • React JS
  • Node JS
  • Angular JS
Node.js runs JavaScript on the server; the others build browser UIs.

A fetch call to your API returns status 404. What happens?

  • the Promise rejects and catch runs
  • the Promise resolves; res.ok is false
  • the page reloads
  • JavaScript stops running
fetch rejects only for network failures. Check res.ok or res.status.

Select all TRUE statements about validation. (select all that apply)

  • Frontend validation gives immediate feedback.
  • Backend validation is mandatory for security.
  • Frontend validation is enough for security.
  • Backend validation is unnecessary if the form has required.
The browser can be bypassed, so the server must always validate.

Viva: say it out loud

Viva: say it out loud
What is the DOM?

It is the browser's in-memory tree of objects built from the HTML. JavaScript can read and change it, and the screen updates. Changing the DOM does not change the HTML file on the server.

Explain the difference between textContent and innerHTML, and which is safer.

textContent sets plain text, so any HTML in it is shown literally. innerHTML parses its value as HTML. If that value comes from a user, an attacker can inject a script (XSS), so I use textContent for user data.

How does your page talk to your Flask backend without reloading?

I use fetch with a JSON API: GET to read, POST with a JSON body and the Content-Type header to create. I await the response, check res.ok, parse res.json() and update the DOM.

What is the difference between server-side and client-side rendering?

In server-side rendering Flask builds finished HTML with Jinja and sends it. In client-side rendering the browser gets JSON and builds the HTML with JS. I use Jinja for the pages and fetch for small live updates, which is progressive enhancement.

Why validate on the server when you already validate in JavaScript?

Anyone can skip the browser and call my API directly. Frontend validation only helps the user; the backend validation is the real protection.

When would you pick localStorage and when cookies?

localStorage for preferences kept in the browser only, because it is never sent to the server. Cookies when the server needs the value on every request, such as a session id.

What is CORS?

A browser rule that stops a page from reading responses from another origin unless that server allows it with Access-Control-Allow-Origin. If my page and API come from the same Flask app, it does not arise.

How do you debug JavaScript?

I open dev tools: the Console for errors and console.log, the Network tab for each request, status and response, and Elements for the live DOM.

Recap

Remember this
  • Load JS with script; use defer or put it at the end of body so elements exist; console.log writes to the console.
  • JS basics: let/const, arrow functions, arrays (map, filter), objects; use ===.
  • DOM: select (getElementById, querySelector), change (textContent, style.fontWeight, classList), build (createElement, appendChild).
  • Events: addEventListener("click", fn); forms use e.preventDefault(); validate on the client for UX and on the server for safety.
  • JSON: JSON.parse and JSON.stringify; fetch needs Content-Type and a stringified body for POST; check res.ok.
  • Render on the server (Jinja) or the client (JS + API); progressive enhancement; Vue is for large UIs.
  • localStorage (browser only) vs cookies (sent every request); CORS is a browser rule; innerHTML with user text is XSS.
Code and facts to keep
  • <script src="app.js" defer></script>
  • document.getElementById("id"), document.querySelector("#id .cls"), querySelectorAll("li")
  • el.textContent, el.innerHTML, el.style.fontWeight = "bold", el.classList.add("x")
  • const li = document.createElement("li"); ul.appendChild(li)
  • el.addEventListener("click", e => { e.preventDefault(); })
  • JSON.parse(text), JSON.stringify(obj)
  • const res = await fetch(url, {method:"POST", headers:{"Content-Type":"application/json"}, body: JSON.stringify(data)}), res.ok, res.status, await res.json()
  • localStorage.setItem(k, v), getItem(k), removeItem(k)
  • display: block, inline, inline-block (side by side), flex (row)
Connected weeks (learn them together)

Practise with real exam questions

These come straight from past papers (with the real figures and the official answer key). Open 60 real questions on this week   All Quiz 2 papers