MAD Study Hub

Home / Viva prep / MAD2 questions

MAD2 viva question bank

282 questions with model answers, grouped by topic. Answer out loud first, then open the answer. Tick the ones you can answer; the count is saved in your browser.

0 / 282 knownStart a mock viva
1What is the difference between var, let and const? L1

var is function-scoped and hoisted with the value undefined. let and const are block-scoped and live in the temporal dead zone until their declaration line runs, so using them earlier throws a ReferenceError. const stops you from re-assigning the variable, but an object or array held in a const can still be mutated. In my code I use const by default and let only when the value must change.

2What is the difference between == and === in JavaScript? L1

== compares after type coercion, so 0 == "0" and null == undefined are true. === compares both type and value with no coercion, so 0 === "0" is false. Strict equality is predictable, so I use === everywhere. The only common use of == is x == null to catch both null and undefined.

3What is a closure? Give a small example. L1

A closure is a function that keeps access to the variables of the scope where it was created, even after that outer function has returned. The variables stay alive because the inner function still references them.

function counter() {
  let n = 0;
  return () => ++n;
}
const next = counter();
next(); // 1
next(); // 2

Here n is private to counter and only next can change it. Closures are what make callbacks, event handlers and module-style privacy work.

4This loop prints 3, 3, 3. Why, and how do you make it print 0, 1, 2? ```js for (var i = 0; i < 3; i++) { setTimeout(() => console.log(i), 0); } ``` L2

var i is a single function-scoped variable shared by all three callbacks. The callbacks run after the loop ends, when i is already 3. Changing var to let creates a fresh binding of i for each iteration, so the closures capture 0, 1 and 2. The older fix is wrapping the body in an IIFE that receives i as a parameter.

5What is hoisting? L1

Before running the code, JavaScript registers declarations in their scope. Function declarations are hoisted completely, so you can call them before they appear. var variables are hoisted and initialised to undefined. let and const are hoisted too, but stay uninitialised in the temporal dead zone, so reading them early throws an error. Function expressions and arrow functions assigned to a variable follow that variable's rule.

6How is an arrow function different from a normal function? L1

An arrow function has no this, arguments or super of its own; it uses the this of the surrounding scope (lexical this). It also cannot be used as a constructor with new. That is why arrows are good for callbacks inside methods, while object methods and Vue 2 methods should be normal functions so that this points to the instance.

7What will `this` be inside a Vue 2 method if you write the method as an arrow function, and what is the fix? ```js methods: { save: () => { this.count++; } // wrong } ``` L2

The arrow function takes this from the enclosing module scope, not from the Vue instance, so this.count is undefined or throws an error. Vue binds methods to the instance, but it cannot rebind an arrow. Write save() { this.count++; } or save: function () { ... }. Arrows are fine for callbacks nested inside a method, because they inherit the method's this.

8What does `this` refer to in JavaScript? L1

this is decided by how a function is called, not where it is written. In a method call obj.f() it is obj; with new it is the new object; with call, apply or bind it is whatever you pass; in a plain call it is undefined in strict mode (or the global object otherwise). Arrow functions do not have their own this and use the surrounding one.

9Explain prototypes and prototype-based inheritance in JavaScript. L1

Every object has an internal link to another object called its prototype. When you read a property that the object does not have, JavaScript walks up this prototype chain until it finds the property or reaches null. Functions used with new set the new object's prototype to Fn.prototype, and class syntax is mostly a cleaner way to write the same mechanism. Methods put on the prototype are shared by all instances instead of copied.

10What is an execution context and the call stack? L1

An execution context is the environment in which code runs: it holds the variables, the scope chain and the value of this. There is a global context, and each function call creates a new one, which is pushed on the call stack and popped when the function returns. JavaScript has a single call stack, so it runs one thing at a time. Asynchronous callbacks only run when the stack is empty.

11What do map, filter, forEach and reduce do? L1

map returns a new array with each element transformed. filter returns a new array with only the elements for which the callback returns true. forEach just runs a function for each element and returns undefined. reduce folds the array into a single value using an accumulator.

const total = [1, 2, 3].reduce((acc, x) => acc + x, 0); // 6
12What are destructuring and the spread operator? L1

Destructuring unpacks values from arrays or objects into variables: const { id, name } = user; or const [a, b] = arr;. Spread ... expands an iterable or object in place, which is handy for copying and merging: const next = { ...state, loading: false }; or [...list, item]. I use spread to produce new objects instead of mutating, for example inside Vuex mutations or when copying a prop to local data.

13What are ES modules and why do we need a bundler in a Vue CLI project? L1

ES modules let each file keep its own scope and share code with export and import. In a plain browser page you need <script type="module"> and real file paths. In a Vue CLI project, webpack bundles the modules, resolves npm packages, compiles .vue files and minifies the output for production. Modules avoid global variable clashes and make dependencies explicit.

14What is the difference between a shallow copy and a deep copy? L1

A shallow copy such as {...obj} or arr.slice() copies only the top level, so nested objects are still shared between the original and the copy. A deep copy duplicates nested structures as well; structuredClone(obj) works for plain data, and JSON.parse(JSON.stringify(obj)) works for JSON-safe data but loses functions, dates and undefined. This matters in Vue and Vuex when you copy state and then edit it: a shallow copy can silently mutate the original.

15What do truthy and falsy mean? List the falsy values. L1

In a condition JavaScript converts a value to a boolean. The falsy values are false, 0, -0, 0n, "", null, undefined and NaN; everything else is truthy, including "0", [] and {}. This is why v-if="items" is true for an empty array, and you should write items.length when you mean non-empty.

16Why does `typeof null` give "object" and how do you check for NaN? L2

typeof null === "object" is a historical bug in the language that was never fixed for compatibility, so test null with x === null. NaN is the only value not equal to itself, so NaN === NaN is false; use Number.isNaN(x). Arrays also report "object", so use Array.isArray(x).

17What are JSON.stringify and JSON.parse used for? L1

JSON.stringify(obj) turns a JavaScript value into a JSON string, and JSON.parse(str) turns a JSON string back into a value. We use them to send a body with fetch (body: JSON.stringify(data)) and to keep objects in localStorage, which only stores strings. JSON.parse throws on invalid input, so wrap it in try/catch when the data is not trusted.

18Explain event bubbling and event delegation. What would the order of handlers be if a click happens on a table cell? L2

When an element is clicked, the event first travels down in a capture phase and then bubbles up from the target through its ancestors, so listeners on the td, then tr, then table, then body fire in that order (for the default bubbling listeners). event.stopPropagation() stops it going further. Event delegation means attaching one listener to a parent and checking event.target, which works well for long or dynamic lists. Capturing listeners (third argument true) run on the way down, before the bubbling ones.

19What are call, apply and bind? L2

All three set this explicitly. fn.call(obj, a, b) runs the function immediately with arguments listed, fn.apply(obj, [a, b]) does the same with an array of arguments, and fn.bind(obj) returns a new function with this fixed for later. bind is useful when passing a method as a callback, such as setTimeout(this.save.bind(this), 100).

20What is the output and why? ```js const o = { name: "A", hi() { return this.name; } }; const f = o.hi; console.log(o.hi(), f()); ``` L2

o.hi() is a method call so this is o and it prints "A". f() is a plain call: this is undefined in strict mode (which throws if you access .name) or the global object in sloppy mode (usually undefined/empty name in a browser). Detaching a method loses its this. Fix with o.hi.bind(o).

21Explain the JavaScript event loop. L1

JavaScript runs on one thread with a call stack. Asynchronous work, such as timers, fetch and DOM events, is handled by the browser, and when it finishes a callback is placed in a queue. The event loop takes the next callback from the queue only when the call stack is empty. Promise callbacks go into the microtask queue, which is fully drained before the next macrotask such as a setTimeout callback.

22What is the order of output and why? ```js console.log("A"); setTimeout(() => console.log("B"), 0); Promise.resolve().then(() => console.log("C")); console.log("D"); ``` L2

The output is A, D, C, B. A and D are synchronous. The promise callback is a microtask and runs right after the current script finishes. The timer callback is a macrotask and runs later, even with a delay of 0.

23What problem do promises solve compared with callbacks? L1

Nested callbacks lead to deeply indented "callback hell" with scattered error handling. A promise represents a future result and can be chained with .then(), with one .catch() for any failure in the chain. It has three states: pending, fulfilled and rejected, and once settled it never changes. async/await is syntax that lets you consume promises in a sequential style.

24What does an async function return, and what does await do? L1

An async function always returns a Promise; a returned value is wrapped in a resolved promise and a thrown error becomes a rejection. await pauses only that function until the awaited promise settles, then gives you the value or throws the rejection. The rest of the program is not blocked, because the function is suspended and the event loop continues.

25How do you handle errors with async/await? L1

Wrap the awaited calls in try/catch (optionally with finally to reset a loading flag), or attach .catch() to the returned promise.

async function load() {
  try {
    const res = await fetch("/api/lots");
    if (!res.ok) throw new Error("HTTP " + res.status);
    return await res.json();
  } catch (e) {
    console.error(e);
  } finally {
    // hide spinner
  }
}
26Does fetch reject the promise when the server returns 404 or 500? L1

No. fetch rejects only on network-level failure such as no connection or a CORS block. A 404 or 500 still resolves with a Response object, so you must check res.ok or res.status yourself. Axios differs here: it rejects for any status outside 2xx by default.

27How do you send JSON with a POST request using fetch? L1
const res = await fetch("/api/login", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ email, password }),
});
const data = await res.json();

res.json() itself returns a promise, so it needs await or .then. Without the Content-Type header, Flask's request.get_json() will not parse the body as JSON by default.

28What is the difference between Promise.all, Promise.allSettled, Promise.race and Promise.any? L1

Promise.all resolves with an array of all results, but rejects as soon as any one promise rejects. Promise.allSettled waits for all and returns each outcome as fulfilled or rejected, never rejecting. Promise.race settles with the first promise to settle, whether success or failure. Promise.any resolves with the first promise that fulfils and only rejects if all of them reject.

29You need three independent API calls. Which is faster, awaiting them one by one or using Promise.all, and show the code? L2

Awaiting one by one runs them sequentially, so total time is the sum of all three. Starting them together and awaiting with Promise.all runs them in parallel, so total time is about the slowest one.

const [lots, users, stats] = await Promise.all([
  fetch("/api/lots").then(r => r.json()),
  fetch("/api/users").then(r => r.json()),
  fetch("/api/stats").then(r => r.json()),
]);

Only do sequential awaits when a call depends on the result of the previous one.

30Why does `items.forEach(async x => { await save(x); })` not wait for the saves to finish? L2

forEach ignores the promises returned by the callback, so it returns immediately and the code after it runs before the saves complete. To run them one after another use for (const x of items) { await save(x); }. To run them in parallel and wait, use await Promise.all(items.map(x => save(x))).

31A team of two works in parallel (3 s and 2 s) and a solo player takes 4 s. How long does Promise.all take, and how long would sequential awaits take? L2

Promise.all finishes when the slowest promise finishes, so for tasks of 3 s and 2 s started together it takes about 3 s. Awaiting them one after another takes about 5 s (3 + 2). The solo 4 s task would beat the sequential team but lose to the parallel team. This shows that parallel promises take the maximum time, sequential ones take the sum.

32What is debouncing and where did you use or would you use it? L1

Debouncing delays running a function until the user has stopped triggering it for a fixed time, for example 300 ms after the last keystroke in a search box. It avoids sending an API request for every key press. In Vue you can implement it with setTimeout and clearTimeout inside a watcher or method.

let t;
function onInput(q) {
  clearTimeout(t);
  t = setTimeout(() => search(q), 300);
}
33What is setTimeout(fn, 0) and does it run immediately? L1

It schedules fn as a macrotask after at least the given delay, so it runs only after the current code and all pending microtasks (promise callbacks) have finished. The delay is a minimum, not a guarantee, because the call stack must be empty first.

34How can you cancel a fetch request, for example when the user types a new search? L2

Create an AbortController, pass its signal to fetch, and call controller.abort() to cancel; the fetch promise rejects with an AbortError, which you should ignore.

const c = new AbortController();
fetch("/api/search?q=abc", { signal: c.signal });
c.abort();

Axios supports cancelling too (newer versions accept the same AbortController signal).

35What happens if a promise rejects and nobody handles it? L2

The browser reports an "unhandled promise rejection" in the console and the failure is easy to miss. Always add .catch() or try/catch around await. In a Vue app you can also add a global handler with window.addEventListener("unhandledrejection", ...) for logging.

36How would you poll an API every few seconds from a Vue page, and what must you remember to do? L1

Start a setInterval in mounted that calls the status endpoint and stores the result in data; stop it with clearInterval once the job is done and also in beforeDestroy. If you forget to clear it, the timer keeps running after the component is gone, causing memory leaks and extra requests.

mounted() { this.timer = setInterval(this.check, 3000); },
beforeDestroy() { clearInterval(this.timer); }
37In the following code, which line prints first, and what is the type of `r`? ```js async function f() { return 5; } const r = f(); console.log(r); ``` L2

It prints Promise { 5 } (a resolved promise), not the number 5, because async functions always wrap their return value. You need await f() or f().then(v => ...) to get 5.

38What is a single-page application (SPA) and how does it differ from a multi-page app? L1

An SPA loads one HTML page and then updates the view in the browser with JavaScript, getting data from APIs as JSON. A traditional multi-page app asks the server for a whole new HTML page on each navigation. SPAs feel faster after the first load and separate frontend from backend, but the first load is heavier and SEO needs extra work.

39What is the difference between client-side and server-side rendering, and which does your project use? L1

In server-side rendering (Flask with Jinja) the server builds the HTML and sends it ready to display. In client-side rendering (Vue) the server sends a small HTML shell plus JavaScript, and the browser builds the page from JSON API data. My project uses Vue for client-side rendering, with Flask serving only JSON APIs plus the static build.

40What is a Progressive Web App (PWA)? L1

A PWA is a web app that can be installed and can work offline or on poor networks. It needs HTTPS, a web app manifest (name, icons, start URL, display mode) and a service worker. The service worker is a script that runs in the background and can intercept network requests and serve cached files.

41What does a service worker do and what are its limits? L2

A service worker sits between the page and the network, so it can cache static files and respond when the user is offline, and it can handle push notifications. It runs in a separate thread, cannot access the DOM directly, works only over HTTPS (localhost is allowed), and has its own lifecycle of install, activate and fetch events. Because it can serve stale files, the cache must be versioned and old caches deleted on activate.

42Compare localStorage, sessionStorage and cookies. L1

localStorage keeps strings with no expiry until cleared, per origin, about 5 MB, and is not sent to the server. sessionStorage is the same but lives only for that tab. Cookies are small (about 4 KB), can have an expiry, and are sent to the server automatically with every matching request; they can be marked HttpOnly so JavaScript cannot read them. Use cookies when the server must receive the value each time, and storage for client-only data.

43What is IndexedDB and when would you use it over localStorage? L1

IndexedDB is an asynchronous, transactional, in-browser database for large amounts of structured data, including files and blobs. localStorage is synchronous, string-only and small. Use IndexedDB for large offline data, for example cached lists in a PWA; use localStorage for tiny settings such as a theme or a token.

44What is ephemeral state in a frontend app? Give examples. L1

Ephemeral state is temporary UI state that is not worth saving: whether a dropdown is open, the current text typed in an input, scroll position or a loading spinner flag. It lives in component data and is lost on refresh, unlike persistent state such as user records, which belong in the database, or the token, which you may keep in localStorage.

45Where do you store the auth token on the client, and what are the risks? L2

I store it in localStorage (and also in Vuex so components can react to it), and send it in an Authorization or Authentication-Token header. The risk is that any injected script (XSS) can read localStorage and steal the token. An HttpOnly cookie cannot be read by scripts, but then you must defend against CSRF using SameSite and CSRF tokens. Either way, keep token lifetime short and escape all user content.

46An application is built entirely on the client with JavaScript and no server. What can and can't it do? L1

It can show UI, do calculations and keep data in the browser with localStorage or IndexedDB. But data is stored per browser and per user, it is not shared across devices or users, and the user can inspect or edit it. Secrets, access control and shared state need a server, since anything in client code is visible to the user.

47What is Lighthouse and what does it measure? L1

Lighthouse is a Chrome DevTools audit tool that scores a page on Performance, Accessibility, Best Practices, SEO and (optionally) PWA checks. The performance score is computed from metrics such as First Contentful Paint, Largest Contentful Paint, Total Blocking Time, Cumulative Layout Shift and Speed Index. It gives suggestions like compressing images, removing unused JavaScript and caching static files.

48What is the DOM, and how is it different from the HTML source? L1

The DOM is the browser's in-memory tree of objects built from the HTML, which JavaScript can read and modify. The HTML source is just text. After scripts change the DOM, what you see on screen can differ from the original file, which is exactly what Vue does when it renders.

49What does it mean that HTTP is stateless and how do web apps still remember the user? L1

Each HTTP request is independent, and the server does not remember earlier requests by itself. Apps add state on top: a session cookie holding an id that points to server-side session data, or a token sent by the client on each request that identifies the user. Token-based auth is common for SPAs and mobile apps because the API stays stateless.

50What is the same-origin policy? L2

The same-origin policy is a browser rule that stops a page from one origin from reading data from another origin. An origin is scheme + host + port, so http://localhost:8080 and http://localhost:5000 are different origins. It protects users from malicious sites reading their data on other sites; CORS is the controlled way for a server to relax it.

51How does the browser decide what to cache, and which headers control it? L2

The server controls it with HTTP headers: Cache-Control (for example max-age=3600, no-store, no-cache, public, private), ETag and Last-Modified for validation, and Expires. With validators the browser sends If-None-Match or If-Modified-Since, and the server may reply 304 Not Modified with no body. This is separate from server-side caching with Redis.

52What makes a web page load slowly, and how does it affect Lighthouse? L1

Large JavaScript bundles and images, render-blocking scripts and CSS, many requests, slow server response (TTFB), no compression or caching, and layout shifts. These directly hurt metrics like LCP, TBT and CLS. Fixes are code splitting, lazy loading, compressing and resizing images, gzip or brotli, caching and a CDN.

53What is Vue.js and what does "progressive framework" mean? L1

Vue is a JavaScript framework for building user interfaces from components, with reactive data binding between state and DOM. "Progressive" means you can adopt it gradually: drop it in with one script tag to enhance a page, or use Vue CLI with the router and Vuex for a full SPA. This course uses Vue 2 (Options API); in newer Vue 3 there is also the Composition API.

54Why did you choose Vue CLI instead of the CDN version (or the reverse)? L1

CDN is just a script tag, with no build step, so it is quick for small pages and demos. Vue CLI gives single-file components (.vue), npm packages, hot reload, ES modules, linting and an optimised production build, which suits a multi-component SPA with Vue Router and Vuex. I would pick CLI for a project with many components and shared state, and say clearly which one I actually used and why.

55What is the difference between v-if and v-show? L1

v-if actually adds or removes the element from the DOM (and supports v-else-if and v-else), so it has a higher cost when toggling but nothing is rendered when false. v-show always renders the element and only toggles display: none, so toggling is cheap but the initial render cost is paid. Use v-show for something toggled often, such as a dropdown, and v-if when the condition rarely changes or the content is heavy. v-show does not work on <template>.

56Why do we use :key with v-for? L1

key gives each item a stable unique identity so Vue can reuse, move or remove the right DOM elements when the list changes, instead of patching in place and mixing up state (like input text). Use a unique id from the data, not the array index when items can be reordered or removed.

<li v-for="lot in lots" :key="lot.id">{{ lot.name }}</li>
57Explain v-model. L1

v-model is two-way binding on form inputs. On a text input it is shorthand for :value="x" plus @input="x = $event.target.value". It has modifiers .trim, .number and .lazy. On a custom component in Vue 2 it binds the value prop and the input event.

58Differentiate data, computed, methods and watch. L1

data holds the reactive state. computed properties are derived values that are cached and recalculated only when their reactive dependencies change. methods run every time they are called (and on each re-render if used in a template). watch runs a function for side effects when a property changes, such as calling an API when a search term changes.

59When would you use computed instead of a method for a filtered list, and write it. L2

Use computed because the result is cached and re-evaluated only when items or query change, whereas a method in the template would run on every re-render.

computed: {
  filtered() {
    return this.items.filter(i =>
      i.name.toLowerCase().includes(this.query.toLowerCase()));
  }
}

Computed must be pure: no API calls or data changes inside it.

60How does reactivity work in Vue 2? L1

Vue 2 walks through the data object and converts each property into a getter and setter using Object.defineProperty. When a render function or computed reads a property, Vue records the dependency, and when the setter runs it tells the dependents to re-run, so the DOM updates. In Vue 3 this uses Proxies instead.

61Why doesn't the page update when you do `this.items[0] = x` or add a new property to an object in Vue 2, and how do you fix it? L2

Vue 2 cannot detect array index assignment or adding or deleting object properties because getters and setters are created only for properties existing at creation. Use this.$set(this.items, 0, x) (or Vue.set), splice, or replace the array or object with a new one: this.user = { ...this.user, age: 20 }. Declare all properties in data up front. (In Vue 3 Proxy-based reactivity removes this limitation.)

62Why must `data` be a function inside a component? L1

Components are reused, and if data were a shared object all instances would share and change the same state. A function returns a fresh object for each instance. The root instance created with new Vue can use an object because it exists only once.

63List the Vue 2 lifecycle hooks in order and say what each is used for. L1

beforeCreate, created (data and methods ready, no DOM yet; good for initial API calls), beforeMount, mounted (DOM is available; use for DOM access or third-party libraries), beforeUpdate, updated (after re-render), beforeDestroy (clean up timers and listeners) and destroyed. In Vue 3 the last two are renamed beforeUnmount and unmounted.

64Where should you fetch the initial data and why? L2

Usually in created or mounted. created fires earlier, before the DOM is mounted, so the request starts sooner and this data is already reactive. mounted is needed only if you must touch the DOM (for example to initialise a chart on a canvas). In both cases show a loading flag, because data arrives after the first render.

65Write the class binding that always applies `errorClass` and applies `activeClass` only when `isActive` is truthy. L2
<div class="errorClass" :class="{ activeClass: isActive }"></div>
<div :class="['errorClass', { activeClass: isActive }]"></div>

A plain class attribute and a :class binding are merged by Vue. The object syntax adds the class when the value is truthy; an array can mix strings and objects.

66What are the shorthands for v-bind and v-on, and what is the difference between {{ }} and v-bind? L1

: is shorthand for v-bind and @ for v-on. Mustache {{ }} interpolates text content only, while v-bind sets attributes and props, for example :href="url" or :disabled="busy". You cannot use mustaches inside attributes.

67What are event modifiers and what are they used for? L1

Modifiers change how an event handler behaves, without code in the method: @submit.prevent calls preventDefault, @click.stop stops propagation, .once runs only once, .self, .capture, and key modifiers like @keyup.enter. They keep methods free of DOM details.

68What is the virtual DOM? L1

It is a lightweight JavaScript tree describing what the UI should look like. On a state change Vue renders a new virtual tree, compares it to the previous one (diffing), and applies only the necessary changes to the real DOM, which is much cheaper than rebuilding everything.

69Why is using v-if and v-for on the same element discouraged? L2

Their priority differs between versions (v-for first in Vue 2, v-if first in Vue 3) and it also makes Vue loop over every item even when most are hidden. Instead, filter in a computed property and loop over the result, or move v-if to a wrapping <template>.

70What is $nextTick and when is it needed? L2

After you change data, Vue updates the DOM asynchronously in a batch. If you need to read or use the updated DOM, for example to focus an input that just appeared via v-if, call this.$nextTick(() => { ... }) (or await this.$nextTick()), which runs after the DOM update.

71What is a template ref and how do you use it? L1

Add ref="name" to an element or component and access it as this.$refs.name after mount, for example this.$refs.search.focus(). Refs give direct access to the DOM or a child instance; prefer props and events for normal data flow because refs are not reactive.

72What is the difference between Vue 2 and Vue 3? L1

Vue 3 adds the Composition API (setup, ref, reactive), Proxy-based reactivity, multiple root nodes in a template, better performance and TypeScript support, and its ecosystem is Vue Router 4 and Pinia (Vuex 4 still exists). Vue 2 uses the Options API and Object.defineProperty. This course works in Vue 2 with Vuex 3 and Vue Router 3.

73How does your Vue frontend talk to the Flask backend? L1

The Vue app calls the Flask REST endpoints (for example /api/lots) using fetch or Axios, sends and receives JSON, and attaches the auth token in a request header. Flask returns JSON and status codes, and Vue stores the result in data or the Vuex store, which triggers a re-render. The frontend and backend are separate apps that share only the API contract.

74Show how you fetch a list from the API in a Vue 2 component. L1
export default {
  data() { return { lots: [], loading: true, error: null }; },
  async created() {
    try {
      const res = await fetch("/api/lots", {
        headers: { "Authentication-Token": localStorage.getItem("token") },
      });
      if (!res.ok) throw new Error("HTTP " + res.status);
      this.lots = await res.json();
    } catch (e) { this.error = e.message; }
    finally { this.loading = false; }
  },
};

The template then uses v-if="loading", v-else-if="error" and v-for over lots.

75Fetch vs Axios: which did you use and why? L1

Fetch is built into the browser, with no dependency, but it needs a manual res.json(), does not reject on HTTP errors, and has no interceptors. Axios parses JSON automatically, rejects on non-2xx, supports default headers, interceptors and cancellation, so it reduces repeated code. Either is fine; the point is to handle errors and attach the token consistently.

76How do you attach the token to every request using an Axios interceptor? L2
import axios from "axios";
const api = axios.create({ baseURL: "http://localhost:5000" });
api.interceptors.request.use(cfg => {
  const t = localStorage.getItem("token");
  if (t) cfg.headers["Authentication-Token"] = t;
  return cfg;
});
api.interceptors.response.use(r => r, err => {
  if (err.response && err.response.status === 401) {
    localStorage.removeItem("token");
    window.location = "/login";
  }
  return Promise.reject(err);
});

The request interceptor avoids repeating the header in each call, and the response interceptor handles expired tokens in one place.

77How do you show loading and error states in the UI while calling an API? L1

Keep loading and error flags in data. Set loading to true before the call, then in finally set it to false, and store a message in error in the catch. In the template show a spinner with v-if="loading", an error message with v-else-if="error", and the list otherwise. This keeps the user informed and avoids a blank page.

78In development the Vue dev server runs on port 8080 and Flask on 5000. How do you avoid CORS errors without changing the backend? L2

Use the Vue CLI dev-server proxy so the browser sends requests to the same origin as the page, and the dev server forwards /api to Flask.

// vue.config.js
module.exports = {
  devServer: { proxy: { "/api": { target: "http://localhost:5000", changeOrigin: true } } },
};

The browser sees only same-origin calls, so no CORS preflight is needed. In production, serve the built files and API from the same domain.

79How is the built Vue app served together with Flask in production? L1

Run npm run build to produce static files in dist/. Flask can serve these as static files, with a route returning index.html for the root (and for all unknown paths when using history mode), while /api/* routes return JSON. Alternatively put Nginx in front: static files straight from disk and /api proxied to Flask.

80Jinja and Vue both use {{ }}. How do you avoid the clash when Flask renders an HTML page that uses Vue from the CDN? L2

Either serve the Vue page as a static file (not through Jinja), wrap the Vue part in {% raw %}...{% endraw %}, or change Vue's delimiters with delimiters: ["[[", "]]"] in the Vue instance. The cleanest solution is what we do in an SPA: Flask serves index.html as a static file and sends only JSON, so Jinja never touches the Vue templates.

81How do you handle a 401 response from the API in the frontend? L2

A 401 means the token is missing, invalid or expired, so the app should clear the stored token and user from Vuex and localStorage and redirect to the login page, ideally in one central Axios or fetch wrapper. A 403 means the user is logged in but not allowed, so show a "not permitted" message and do not log out.

82How do you implement pagination and search from the frontend? L2

Send page, per_page and q as query parameters, fetch("/api/lots?page=2&q=park"), and let the backend apply LIMIT/OFFSET (for example paginate() in Flask-SQLAlchemy) and return the items with total count. The frontend keeps the page number in data or in the route query, and a debounced watcher on the search text reloads the first page. This keeps payloads small and fast.

83How do you upload a file or submit a form from Vue to Flask? L2

For a file use FormData: const fd = new FormData(); fd.append("file", this.$refs.file.files[0]); await axios.post("/api/upload", fd); and do not set Content-Type manually, so the browser adds the multipart boundary. In Flask read it with request.files["file"]. For plain forms use @submit.prevent on the form and send JSON.

84How do you configure the API base URL for development and production in Vue CLI? L2

Put it in environment files such as .env.development and .env.production; Vue CLI exposes only variables prefixed with VUE_APP_, for example VUE_APP_API=http://localhost:5000, and you read them as process.env.VUE_APP_API. This keeps URLs out of the code, and secrets must never go there because they end up in the public bundle.

85Why call APIs from a Vuex action instead of directly in a component? L2

Actions centralise async logic and error handling, avoid repeating the same fetch in several components, and make the data available to every component through the store. The action calls the API and commits a mutation with the result, so components only dispatch the action and read state. For simple local data used by only one component, calling in the component is fine.

86What is the difference between how Jinja and Vue render? L1

Jinja renders on the server: Flask fills in the template and sends finished HTML. Vue renders in the browser from JavaScript and JSON, so navigation and updates need no full reload. In my project Jinja is not used for pages, except possibly for email or PDF report templates generated on the server.

87How do parent and child components communicate in Vue? L1

Down with props, up with events. The parent passes data through props (<child :title="t">), and the child sends a message by calling this.$emit("saved", payload), which the parent listens to with @saved="onSaved". This one-way flow keeps data changes traceable.

88Why shouldn't a child mutate a prop? L1

Props follow one-way data flow: the parent owns the data. If a child changes it, the parent's state is not updated, and the next re-render overwrites the change; Vue also warns about it. Instead, copy the prop into local data when you need a local editable copy, or emit an event so the parent updates its own state.

89Write a child component with a prop and an emitted event, and the parent that uses it. L2
Vue.component("lot-card", {
  props: { lot: { type: Object, required: true } },
  template: `<div>{{ lot.name }}
    <button @click="$emit('book', lot.id)">Book</button></div>`,
});
<lot-card v-for="l in lots" :key="l.id" :lot="l" @book="book"></lot-card>

book(id) in the parent receives the emitted id. Event names are best written in kebab-case in templates.

90What are slots? L1

Slots let a parent pass template content into a child component. A default <slot></slot> renders whatever is placed between the child's tags, named slots (<slot name="header"> with <template v-slot:header>) place content in specific spots, and scoped slots let the child pass data back to the slot content. They make components flexible, like a card or modal with customisable content.

91How do you validate props? L2

Declare props as an object with types, defaults and validators.

props: {
  count: { type: Number, default: 0 },
  status: { type: String, validator: v => ["open", "closed"].includes(v) },
  user: { type: Object, default: () => ({}) },
}

Defaults for objects and arrays must be returned from a function. Invalid props only produce a console warning in development.

92What is the difference between global and local component registration? L1

Vue.component("name", {...}) registers globally, so it can be used in any template but is always bundled. Local registration uses components: { Name } inside a component or view, so it is available only there and works better with tree shaking. Prefer local for components used in few places.

93How does v-model work on a custom component in Vue 2? L2

By default the component receives a value prop and emits an input event, so <my-input v-model="name"> is shorthand for :value="name" @input="name = $event". Inside the component you emit this.$emit("input", newValue). You can rename these with the model: { prop, event } option. Vue 2 also has the .sync modifier (:title.sync="t" with $emit("update:title", v)); in Vue 3 v-model uses modelValue and update:modelValue.

94How do two sibling components share data? L1

Move the shared state to their common parent and pass it down as props with events up; for many components, or deep trees, use a Vuex store. An event bus (a separate Vue instance with $on and $emit) works in small apps but becomes hard to track, and it was removed in Vue 3.

95What is provide/inject and when would you use it instead of props? L2

provide in an ancestor makes values available, and inject in any descendant reads them without passing props through each level (prop drilling). It is useful for library-style components such as tabs, but the data flow is less visible and the injected values are not reactive by default in Vue 2, so for app-wide state a Vuex store is clearer.

96What does `scoped` do in a single-file component's style tag? L2

<style scoped> adds a unique data attribute to the component's elements and rewrites the CSS selectors so that the styles apply only to this component and do not leak. Child component root elements are also affected by the parent's scoped styles. To style deeper inside a child you use a deep selector (>>> or /deep/ in Vue 2).

97What is a single-file component (SFC)? L1

A .vue file that contains a <template>, a <script> that exports the component options, and a <style>. A build tool such as webpack with vue-loader compiles it. SFCs keep markup, logic and styles of one component together and are why we need Vue CLI rather than the plain CDN.

98What does the `key` attribute do on a component, and how can it be used to reset one? L2

key identifies the component to Vue's diff. If the key changes, Vue destroys the old instance and creates a new one, so its internal state is reset. For example <edit-form :key="user.id"> gives a fresh form each time the selected user changes.

99What is a dynamic component and when do you use it? L2

<component :is="currentTab"></component> renders the component whose name or object is in currentTab, useful for tabs or wizards. Wrap it in <keep-alive> to keep a deactivated component's state instead of destroying it.

100How would you build a reusable modal that lets the parent decide its content and handle close? L2

Make a Modal component with a <slot> for the body, a visible prop, and a close event. The parent controls visible with its own data and listens to @close.

<modal :visible="show" @close="show = false">
  <p>Delete this lot?</p>
</modal>

This keeps the modal generic: the content comes from the slot and the state remains in the parent.

101Why should you avoid this.$parent and this.$refs for communication? L2

$parent couples the child tightly to a specific parent, so the component can no longer be reused elsewhere. $refs are not reactive and exist only after mounting. Props and events keep dependencies explicit and make components testable; use $refs only for direct DOM actions like focus.

102What is Vue Router and why use it in an SPA? L1

Vue Router (version 3 for Vue 2) maps URL paths to components so a single-page app can have multiple "pages" with working back and forward buttons and shareable links, without reloading. You declare routes, add <router-view> where the matched component should appear, and navigate with <router-link> or this.$router.push.

103Show a basic Vue Router 3 setup. L2
import Vue from "vue";
import VueRouter from "vue-router";
Vue.use(VueRouter);
const routes = [
  { path: "/", component: Home },
  { path: "/lot/:id", name: "lot", component: Lot, props: true },
  { path: "*", component: NotFound },
];
export default new VueRouter({ mode: "history", routes });

The router is passed to new Vue({ router, render: h => h(App) }). The wildcard route must be last.

104What is the difference between router-link and an anchor tag? L1

<router-link to="/lots"> navigates inside the app through the history API without a page reload and keeps the Vue state, and it adds active classes automatically. A normal <a href> makes the browser request a new page from the server, reloading the whole app and losing the in-memory state.

105What is the difference between hash mode and history mode? L1

Hash mode puts the route after # (/#/lots), which the server never sees, so it works with any static server. History mode uses clean paths (/lots) via the HTML5 history API, but a page refresh on /lots makes the browser ask the server for that path, so the server must fall back to index.html for unknown routes. In Flask add a catch-all route that returns index.html.

106How do you read dynamic route parameters and query strings, and what is the difference? L2

Params are part of the path: for /lot/:id read this.$route.params.id. Query is after ?: for /lots?page=2 read this.$route.query.page. Both come back as strings. If the same component is reused when only the param changes, the component is not recreated, so watch $route or use beforeRouteUpdate.

107How do you navigate programmatically with parameters? L2
this.$router.push({ name: "lot", params: { id: 5 } });
this.$router.push({ path: "/lots", query: { page: 2 } });
this.$router.replace("/login");
this.$router.go(-1);

push adds a history entry, replace replaces the current one, and go(-1) goes back. When using path the params option is ignored, so use name with params, or build the path string yourself.

108What are navigation guards? Show how you protected the admin pages. L2

Guards run before a navigation completes and can allow, redirect or cancel it. A global beforeEach checks the token and role.

router.beforeEach((to, from, next) => {
  const token = localStorage.getItem("token");
  const role = localStorage.getItem("role");
  if (to.meta.requiresAuth && !token) return next("/login");
  if (to.meta.role && to.meta.role !== role) return next("/");
  next();
});

Routes carry meta: { requiresAuth: true, role: "admin" }. The callback must always call next() exactly once.

109If you hide admin routes using a guard, is the app secure? L2

No. A guard only controls the UI, and the user can edit the JavaScript, the localStorage values, or call the API directly with curl. Real security is on the server: every admin API verifies the token and the role (@roles_required("admin")) and returns 401 or 403. Front-end guards just give a good user experience.

110What are nested routes? L1

A route can have children, which render inside a second <router-view> in the parent component, like a dashboard layout with a sidebar and a changing content area. Child paths are relative to the parent, for example /admin/users.

111How do you lazy-load a route and why? L2

Use a dynamic import: { path: "/admin", component: () => import("./views/Admin.vue") }. Webpack splits that component into a separate chunk that downloads only when the route is visited, so the first load is smaller and faster. It is a good fit for rarely used pages like admin reports.

112How do you set up redirects, a 404 page and named routes? L2

Use { path: "/home", redirect: "/" } for redirects, { path: "*", component: NotFound } as the last route to catch unknown paths, and name: "lot" on a route to navigate with { name: "lot", params: {...} } so changes to the path do not break links.

113Which options do you have to pass route params as props, and why? L2

Set props: true on the route so :id arrives as a prop, props: route => ({ page: Number(route.query.page) || 1 }) for custom mapping. This decouples the component from $route, so it can be reused and tested with plain props.

114What does an exam-style route like `/slot/:flag` with a query `?offset=1` render? How do you reason about it? L2

Match the path to a route first, then read $route.params.flag as the string "true" (not the boolean) and $route.query.offset as the string "1". Then follow the template of that component, remembering that strings are always truthy and that {{ }} shows what is in params and query exactly. Check whether the path matches a nested child, or falls to the wildcard.

115What is Vuex and why did you use it? L1

Vuex (version 3 for Vue 2) is a centralised store for shared state with strict rules for how it changes. Without it, passing the logged-in user, token or cached lists through many levels of props and events gets messy. With Vuex any component reads the same state and updates it only through mutations, so state changes are predictable and debuggable in devtools. For a small app with a few components, local state and props are enough.

116Explain state, getters, mutations and actions. L1

state is the single source of data. getters are computed properties of the store (such as a filtered list). mutations are the only way to change state, and they must be synchronous. actions can be asynchronous (API calls), and they commit mutations. Components call this.$store.dispatch("fetchLots"), an action calls the API and does commit("SET_LOTS", data), and the mutation assigns state.lots.

117What is the correct sequence for updating Vuex state after an asynchronous API call? L1

The component dispatches an action; the action performs the async work (API request) and waits for it; the action commits a mutation with the result; the mutation changes the state; the components that use that state re-render. Only the mutation touches state directly.

118Why must mutations be synchronous? L1

Devtools record a snapshot before and after each mutation, which only works if the state change finishes at once. An asynchronous change would make it impossible to know when and why the state changed. Put async code in actions, and let the action commit when the result is ready.

119Write a Vuex store with state, a mutation, a getter and an async action for loading lots. L2
export default new Vuex.Store({
  state: { lots: [], token: localStorage.getItem("token") },
  getters: { available: s => s.lots.filter(l => l.free > 0) },
  mutations: {
    SET_LOTS(state, lots) { state.lots = lots; },
  },
  actions: {
    async fetchLots({ commit }) {
      const res = await fetch("/api/lots");
      commit("SET_LOTS", await res.json());
    },
  },
});

A component uses this.$store.state.lots, this.$store.getters.available and this.$store.dispatch("fetchLots").

120How do you use mapState, mapGetters, mapMutations and mapActions? L2

They generate component members from the store so you do not write this.$store... repeatedly.

import { mapState, mapActions } from "vuex";
computed: { ...mapState(["lots"]) },
methods: { ...mapActions(["fetchLots"]) },

State and getters map to computed, while mutations and actions map to methods. The object spread operator is needed to combine them with other local properties.

121In a component you must call the mutation `update_total_cost` with a value. How? L2

Use this.$store.commit("update_total_cost", value) or ...mapMutations(["update_total_cost"]) and then call this.update_total_cost(value). Mutations are called with commit, never dispatch; dispatch is for actions. A mutation receives (state, payload), and when you need several values pass them as one object.

122How do you keep the login state after a page refresh with Vuex? L2

Vuex state lives in memory and is lost on refresh. So when logging in, also save the token (and role) to localStorage, and initialise the store from it: token: localStorage.getItem("token"). On logout clear both. A plugin like vuex-persistedstate can automate this.

123When would you not use Vuex? L1

When state is local to one component or passes only between a parent and a child, props and events are simpler. Vuex adds boilerplate, so it makes sense for data shared across many unrelated components: the current user, token, cart, notification counts and cached lists.

124What are Vuex modules and namespacing? L2

Modules split a large store into several parts such as auth and lots, each with its own state, mutations, actions and getters. With namespaced: true they are accessed as auth/login, so action names do not clash: this.$store.dispatch("auth/login", creds).

125Why is it wrong to modify store state directly from a component? L2

Direct changes bypass mutations, so devtools cannot track them, and in strict mode (strict: true, used in development) Vuex throws an error. Keeping all writes in mutations makes it easy to find who changed what. The component should commit a mutation or dispatch an action.

126What changes in newer Vue regarding Vuex? L1

In newer Vue (Vue 3) the officially recommended store is Pinia, which has no mutations (you change state in actions or directly), simpler syntax and better TypeScript support. Vuex 4 still exists for Vue 3. Our course uses Vuex with Vue 2, so I explain state, getters, mutations and actions.

127How would you make an action return something to the component, such as a success flag? L2

An action always returns a promise, so you can return values and the component can await it.

async login({ commit }, creds) {
  const res = await axios.post("/login?include_auth_token", creds);
  commit("SET_TOKEN", res.data.response.user.authentication_token);
  return true;
}

In the component: const ok = await this.$store.dispatch("login", creds); if (ok) this.$router.push("/");. If the call throws, the rejection reaches the component's try/catch.

128Reading from a store getter versus computing in the component: which is better? L2

Put derived data that several components need in a getter, so the logic lives once and is cached while the state is unchanged. Use a component-level computed for logic that is specific to one component, such as sorting for its own view.

129What is the difference between session-based and token-based authentication? L1

In session auth the server stores the session data and gives the browser a session id in a cookie, which is sent automatically and looked up on each request. In token auth the server issues a signed token after login, the client stores it and sends it in a header on every request, and the server verifies it without keeping session state. Tokens suit SPAs, mobile clients and multi-server setups; sessions are simple for server-rendered apps.

130Which authentication did you use in your project and how does the login flow work? L1

The user posts credentials to the login endpoint, the server verifies the hashed password and returns a token and role. Vue saves the token (Vuex plus localStorage), and every API call sends it in the Authentication-Token (Flask-Security) or Authorization: Bearer header (JWT). The server decorator @auth_required("token") or @jwt_required() checks it, and role decorators restrict admin routes. Logout removes the token from the client.

131What is a JWT and what are its parts? L1

A JSON Web Token is header.payload.signature, three Base64URL parts. The header names the algorithm (like HS256), the payload carries claims such as sub, role, iat and exp, and the signature is made with the server's secret key. The payload is only encoded, not encrypted, so anyone can read it: never put passwords or secrets in it. The signature stops tampering.

132How does the server verify a JWT? L1

It recomputes the signature over the header and payload with its secret key (HS256) or checks it with a public key (RS256), compares it with the signature in the token, and checks claims such as exp. If the signature is wrong or the token expired, it rejects with 401. No database lookup is needed, which is why JWTs are called stateless.

133How does Flask-Security-Too token authentication work? L2

You enable it with a SQLAlchemyUserDatastore; the login endpoint (POST /login?include_auth_token, JSON body) returns an authentication token for the user. The client sends it in the Authentication-Token header, and routes use @auth_required("token") plus @roles_required("admin"). The token is a signed value checked by the server, and it can expire with the SECURITY_TOKEN_MAX_AGE setting.

134Show a protected Flask route with role-based access using Flask-Security. L2
from flask_security import auth_required, roles_required, current_user

@app.route("/api/admin/users")
@auth_required("token")
@roles_required("admin")
def users():
    return jsonify([u.email for u in User.query.all()])

auth_required returns 401 when the token is missing or invalid, and roles_required returns 403 when the user lacks the role. current_user gives the logged-in user, so I can also scope data to the user.

135Show a JWT login and a protected route using Flask-JWT-Extended. L2
from flask_jwt_extended import create_access_token, jwt_required, get_jwt_identity

@app.post("/api/login")
def login():
    user = check_credentials(request.json)
    if not user:
        return {"msg": "bad credentials"}, 401
    return {"token": create_access_token(identity=str(user.id))}

@app.get("/api/me")
@jwt_required()
def me():
    return {"id": get_jwt_identity()}

The token lifetime is set with JWT_ACCESS_TOKEN_EXPIRES, and the signing key with JWT_SECRET_KEY.

136Where do you store the token on the client, and what are the trade-offs? L1

localStorage is simple and works across tabs, but any XSS can read it. An HttpOnly, Secure, SameSite cookie cannot be read by scripts, but is sent automatically and so needs CSRF protection. I choose localStorage plus Vuex with a short expiry and escaped output, and explain this trade-off.

137Why do tokens have an expiry, and what are refresh tokens? L1

If a token is stolen, an expiry limits how long it can be used. A short-lived access token (minutes) is paired with a longer-lived refresh token that is used only to get a new access token. If the access token expires, the client uses the refresh token, or sends the user back to the login page. This balances security and convenience.

138How would you log out with a stateless JWT, and what is the limitation? L2

On the client, delete the token from Vuex and localStorage. The server cannot forget a stateless token, so it stays valid until it expires; to truly revoke it you keep a blocklist of revoked token ids (jti) in Redis until they expire, or use short lifetimes. That is a trade-off between stateless simplicity and revocation.

139How are passwords stored and what is a rainbow table? L1

Passwords are never stored in plain text; they are salted and hashed with a slow algorithm such as bcrypt, PBKDF2 or Argon2 (Flask-Security does this through passlib). A rainbow table is a precomputed table of hashes for common passwords, used to reverse unsalted hashes. A random salt per password makes such tables useless, and a slow hash makes brute force expensive.

140What is the difference between authentication and authorisation, and what is RBAC? L1

Authentication checks who you are (login with credentials or a token), authorisation checks what you may do. Role-Based Access Control grants permissions through roles such as admin, professional and customer, and each endpoint specifies which roles are allowed. It is easier to manage than permissions assigned per person.

141What is the difference between status codes 401 and 403? L1

401 Unauthorized means that authentication is missing or invalid, such as no token, a bad token or an expired token, so the client should log in again. 403 Forbidden means that the user is authenticated but not allowed to do this action, for example a customer calling an admin endpoint.

142What is CSRF and why is token-in-header authentication largely safe against it? L2

Cross-Site Request Forgery tricks a logged-in user's browser into sending an unwanted request to your site, such as a malicious page containing <img src="http://site/delete?id=1">. It works because browsers attach cookies automatically. A token stored in localStorage and sent manually in a header is not attached by the browser to a cross-site request, so the attack fails. If you use cookies for auth, use SameSite cookies and a CSRF token; the CSRF token is a random value the attacker's page cannot read, which the server must check.

143How does Vue escape output and what is XSS? L1

Cross-Site Scripting injects attacker script into your page, for example through a comment containing <script>. Vue's {{ }} interpolation escapes HTML by default, so it is safe; v-html inserts raw HTML and must never be used with untrusted user content. Server-side, return JSON, not HTML, and sanitise rich text if you need to allow it.

144How do you prevent SQL injection in your Flask backend? L2

Use the SQLAlchemy ORM or parameterised queries so user input is sent as bound parameters, never concatenated into SQL strings. For example User.query.filter_by(email=email) is safe, whereas db.session.execute("... WHERE email='" + email + "'") is vulnerable.

145What would you check to make a token-based API secure? L2

Use HTTPS, hash passwords with a salt, keep the secret key out of source control (environment variable), set a short token expiry, validate input, check roles on the server for every endpoint, check object ownership (a user can only see their own bookings), restrict CORS origins, and rate-limit the login endpoint.

146Where should the secret key or other secrets live? L2

In environment variables or a config file that is not committed to git, for example app.config["SECRET_KEY"] = os.environ["SECRET_KEY"]. A leaked signing key lets anyone forge tokens. Frontend environment variables are public in the bundle, so no secrets belong there.

147A user changes `role` in localStorage to "admin". What happens? L2

The frontend might show the admin menu, but every API call goes to the server, which determines the role from the verified token and the database, not from what the client says, so admin endpoints return 403. This is why the role in localStorage is only for UI, and the server must be the real enforcement.

148What is CORS and why does it exist? L1

Browsers apply the same-origin policy: JavaScript on one origin cannot read responses from another origin. CORS (Cross-Origin Resource Sharing) is the mechanism by which a server tells the browser, through Access-Control-Allow-* response headers, which other origins may read its responses. It exists to protect users' data on other sites, and it is enforced by the browser, not the server.

149What is an origin? Which of these origins are the same as https://example.com? L1

An origin is the combination of scheme, host and port. https://example.com and https://example.com/page are the same origin, but http://example.com (different scheme), https://api.example.com (different host) and https://example.com:8443 (different port) are different origins. By default the browser allows fetch calls that read the response only to the same origin; others need CORS headers.

150How do you prevent CORS errors in your project? L1

In development, either configure the backend with Flask-CORS to allow the Vue dev origin, or use the dev-server proxy so calls become same-origin. In production serve the frontend and API from the same domain, or allow exactly the frontend origin. Also make sure the Authorization or Authentication-Token header is allowed, because custom headers trigger a preflight.

151Show how you enable CORS in Flask. L2
from flask_cors import CORS
CORS(app, resources={r"/api/*": {"origins": ["http://localhost:8080"]}},
     allow_headers=["Content-Type", "Authentication-Token", "Authorization"])

CORS(app) alone allows all origins, which is fine for local testing but should be limited in production. Flask-CORS adds the Access-Control-Allow-Origin header and answers the OPTIONS preflight requests.

152What is a preflight request? L1

For non-simple cross-origin requests (methods like PUT or DELETE, a JSON content type, or custom headers such as Authorization) the browser first sends an OPTIONS request asking if the real request is allowed. The server answers with Access-Control-Allow-Origin, Access-Control-Allow-Methods and Access-Control-Allow-Headers; only then the browser sends the actual request. Access-Control-Max-Age lets the browser cache the preflight result.

153Why does `Content-Type: application/json` trigger a preflight but a plain form POST doesn't? L2

The CORS "simple request" rules allow GET, HEAD and POST with only a few content types (application/x-www-form-urlencoded, multipart/form-data, text/plain) and no custom headers. application/json and headers such as Authorization are outside that list, so the browser must ask permission first with an OPTIONS preflight.

154Is CORS a security feature of your server? Will it stop curl or Postman? L2

No. CORS is enforced only by browsers to protect the user; the server still receives the request and curl or Postman ignore CORS completely. So CORS is not access control. Authentication and authorisation must still be done on the server.

155You get "No 'Access-Control-Allow-Origin' header is present" in the browser console while the same request works in Postman. What do you check? L2

It means that the response lacked the CORS header, so I check that the Flask-CORS or the headers are configured for this route and origin, that the preflight (OPTIONS) is handled and returns 200/204, and that the server did not crash with a 500 (error responses may lack CORS headers). Also check that the origin string matches exactly, including port and scheme. It works in Postman only because Postman does not enforce CORS.

156How do cookies work with CORS? L2

For a cross-origin request to send and accept cookies, the client must opt in (fetch(url, { credentials: "include" }) or axios.defaults.withCredentials = true), and the server must send Access-Control-Allow-Credentials: true with a specific origin, because * is not allowed with credentials. The cookie's SameSite setting also must permit cross-site sending.

157Explain the cookie attributes HttpOnly, Secure and SameSite. L1

HttpOnly hides the cookie from JavaScript (document.cookie), reducing the impact of XSS. Secure sends it only over HTTPS. SameSite controls cross-site sending: Strict never sends it on cross-site requests, Lax sends it on top-level navigations with safe methods, and None (which requires Secure) allows all cross-site use. Together they protect against theft and CSRF.

158What is a session cookie? L1

A session cookie has no expiry date, so the browser normally discards it when the browser session ends, and it commonly holds just a session id that the server maps to stored data. Persistent cookies have an Expires or Max-Age. A session cookie is sent automatically with every request to its domain, which is why CSRF protection matters.

159Why is `*` not allowed as an allowed origin when using credentials? L2

If any website could read credentialed responses, a malicious site could make the user's browser send their cookies and read the private response. So the spec requires an explicit origin in Access-Control-Allow-Origin when Allow-Credentials is true. Flask-CORS with supports_credentials=True echoes the specific allowed origin.

160How would a malicious site exploit an endpoint `GET /delete?id=1` and how do you stop it? L2

The attacker page includes <img src="http://example1.com/delete?id=1">; the browser sends the request with the victim's cookies, so the delete happens (CSRF). Prevent it by never using GET for state changes (use DELETE or POST), by requiring a CSRF token or a token header that a cross-site page cannot add, and by setting SameSite on cookies.

161What is a REST API and what are its main principles? L1

REST is an architectural style where resources are identified by URLs and manipulated through standard HTTP methods. It is stateless (each request carries all needed information), uses a uniform interface, and transfers representations such as JSON. Example: GET /api/lots, POST /api/lots, GET /api/lots/3, PUT /api/lots/3, DELETE /api/lots/3.

162Explain the HTTP methods and which are safe or idempotent. L1

GET reads and is safe and idempotent. POST creates and is neither safe nor idempotent. PUT replaces a resource and is idempotent, as is DELETE; PATCH makes a partial update. Idempotent means repeating the request gives the same final state, which is why clients can safely retry PUT and DELETE.

163Which HTTP status codes do you use and when? L1

200 OK, 201 Created for a successful POST, 204 No Content (for example a DELETE), 400 Bad Request for invalid input, 401 when not authenticated, 403 when not permitted, 404 when not found, 409 for a conflict such as a duplicate or an already-booked spot, 422 for validation errors in some APIs, and 500 for a server bug. Returning correct codes lets the frontend handle errors properly.

164Show a Flask-RESTful resource with GET and POST. L2
from flask_restful import Api, Resource, fields, marshal_with
api = Api(app)
lot_fields = {"id": fields.Integer, "name": fields.String, "price": fields.Float}

class LotList(Resource):
    @marshal_with(lot_fields)
    def get(self):
        return Lot.query.all()

    def post(self):
        d = request.get_json()
        lot = Lot(name=d["name"], price=d["price"])
        db.session.add(lot); db.session.commit()
        return {"id": lot.id}, 201

api.add_resource(LotList, "/api/lots")

marshal_with converts SQLAlchemy objects to JSON-safe dictionaries.

165Why do you need serialisation (marshalling) in an API? L1

SQLAlchemy objects cannot be turned into JSON directly. We convert them to dictionaries with chosen fields, using marshal_with, Marshmallow schemas, or a to_dict() method, so we control what is exposed (never the password hash) and the format the frontend gets.

166How do you validate input in your API? L2

Read the JSON with request.get_json(), check required fields, types and ranges, and return 400 with a clear JSON message when invalid, before touching the database. Validate on the server even if the form is validated in Vue, because the client can be bypassed. Flask-RESTful's reqparse or Marshmallow can automate it.

167How would you design pagination, filtering and sorting in the API? L2

Use query parameters: GET /api/lots?page=2&per_page=10&q=mall&sort=price. The server applies filters and paginate(), and returns a body like { "items": [...], "page": 2, "total": 57 }. Limiting per_page to a maximum protects the server.

168Why is returning a different status code for each failure better than always returning 200 with an error message? L2

HTTP clients, browsers, caches and tools like Axios understand status codes: with Axios a 4xx or 5xx rejects the promise, so error handling goes in the catch block. Always returning 200 hides failures and makes caching, monitoring and debugging harder.

169How do you handle two users booking the same slot at the same time? L2

Check and update in one database transaction, and let the database enforce it, for example with a unique constraint or SELECT ... FOR UPDATE style locking or an atomic conditional update (UPDATE ... WHERE status='free') and check the affected row count. If the second user's attempt finds the slot taken, return 409 Conflict. A simple "check then insert" without a transaction has a race condition. SQLite serialises writes, but the logic still needs to be atomic.

170What is API versioning and why would you do it? L1

Versioning (like /api/v1/lots) lets you change the API without breaking existing clients, which keep using the old version. For a course project it is optional, but I would mention it as a good practice when clients such as a mobile app depend on the API.

171Difference between PUT and PATCH, and which does your update endpoint use? L2

PUT replaces the whole resource, so the client sends all fields; PATCH changes only the fields sent. Many projects use PUT for edits and just update the supplied fields. State which you use and be consistent; both should return the updated resource or 204.

172How do you document or test your API? L1

Test with Postman, curl or the browser, and write down endpoint, method, body, auth header and response codes (for example in a README or an OpenAPI/Swagger file). Consistent documentation helps the frontend developer and the examiner understand the API contract.

173What is rate limiting and where would it be useful in your app? L2

Rate limiting caps how many requests a client can make in a period, protecting against abuse and brute force. It is most useful on the login endpoint and on expensive endpoints such as export. Flask-Limiter can do it, and it can use Redis to store the counters.

174How can you make an API "stateless" and why is it good for scaling? L2

Do not keep per-client data in server memory between requests; each request carries the token and everything needed. Then any server instance can handle any request, so you can add more servers behind a load balancer. Shared state lives in the database or Redis.

175What is the purpose of an API for the frontend/backend split? L1

The API is a contract between the two sides: the backend exposes data and operations as JSON endpoints, and any client (Vue app, mobile app, scripts) can use them. The teams can work independently and the backend can be reused, which is the reason to move from Jinja-rendered pages to Vue.

176What is Redis and what did you use it for? L1

Redis is an in-memory key-value data store that supports strings, hashes, lists, sets and sorted sets, with optional persistence. In the project I used it in two roles: as the message broker (and result backend) for Celery, and as a cache for expensive API responses. Because data is in RAM, reads and writes are very fast.

177Why is Redis fast, and how is it different from a relational database like SQLite? L1

Redis keeps data in memory, uses simple data structures with direct key access, and avoids query parsing and disk reads. A relational database stores data durably on disk with schemas, joins and transactions, and handles complex queries. Redis is used for fast, often temporary data (cache, queues, counters), the SQL database as the source of truth.

178Show some basic Redis commands. L1
redis-cli ping            # PONG
redis-cli set lots:1 "data"
redis-cli get lots:1
redis-cli expire lots:1 60
redis-cli ttl lots:1      # seconds left
redis-cli keys "*"
redis-cli del lots:1

SETEX key seconds value sets a value with expiry at once. KEYS * is fine for debugging but should not be used on big production databases (use SCAN).

179What is a cache and why do you cache? L1

A cache stores the result of an expensive operation, such as a database query or computation, so the next request can be answered quickly. This reduces response time and database load. The cost is that data may be stale, so we need an expiry and invalidation strategy.

180How did you implement caching in Flask, and how would you show it works? L2

With Flask-Caching using a Redis backend.

from flask_caching import Cache
cache = Cache(app, config={"CACHE_TYPE": "RedisCache",
                           "CACHE_REDIS_URL": "redis://localhost:6379/1",
                           "CACHE_DEFAULT_TIMEOUT": 300})

@app.get("/api/stats")
@cache.cached(timeout=60)
def stats():
    return jsonify(compute_stats())

To prove it works: call the endpoint twice and show the second response is much faster, add a print in compute_stats that appears only once, and show the key with redis-cli keys "*" and its decreasing ttl.

181What is the difference between `@cache.cached()` and `@cache.memoize()`? L2

cached builds the key from the request path (by default not from function arguments, and you can use query_string=True to include the query), so it suits a view with no arguments. memoize includes the function's arguments in the key, so get_user(1) and get_user(2) are cached separately, and you can invalidate them with cache.delete_memoized(func, args).

182Two requests go to /compute/10/20 and /compute/5/10 within 60 seconds, and the view is decorated with `@cache.cached(timeout=60)`. What does the second request return? L2

If the route has URL parameters, the default key is based on the request path, so each URL path gets its own entry: /compute/10/20 and /compute/5/10 are different keys, so each computes its own result on the first call and is cached after. If you wrongly used a fixed key_prefix (the same key for both), the second request would return the first request's cached response. Reading the key rule carefully decides the answer.

183Why do we set an expiry (TTL) for a cache? L1

The expiry bounds how stale data can be and keeps memory from filling up. Fast-changing data such as available slots needs a short TTL (a few seconds or minutes), while rarely changing data such as a category list can use a long one. I choose the TTL according to how often the data changes and how bad stale data is.

184What is cache invalidation, and how did you handle it? L1

Invalidation means removing or updating cached data when the underlying data changes, otherwise users see stale values. After a successful POST, PUT or DELETE I delete the related keys with cache.delete("lots") or cache.delete_memoized(get_lots), so the next read repopulates it. A TTL acts as the fallback in case invalidation is missed.

185How do you design cache keys so one user doesn't see another user's data? L2

Include everything that makes the response different in the key: the user id (or role), the path, and the query parameters. For per-user data use key_prefix=lambda: f"bookings:{current_user.id}" or memoize with the user id as an argument. A key that omits the user would serve the first user's data to everybody, which is a serious leak.

186What is a cache hit and a miss, and what is the cache-aside pattern? L1

A hit means the data is found in the cache; a miss means it is not, and we must load it from the database. In cache-aside the application first checks the cache; on a miss it reads the database, stores the result in the cache with a TTL and returns it. Writes update the database and invalidate the cache key.

187Show a manual cache-aside implementation with the redis library. L2
import json, redis
r = redis.Redis(host="localhost", port=6379, db=1)

def get_lots():
    cached = r.get("lots")
    if cached:
        return json.loads(cached)
    data = [l.to_dict() for l in Lot.query.all()]
    r.setex("lots", 60, json.dumps(data))
    return data

After a write I call r.delete("lots"). Redis returns bytes, which json.loads accepts.

188What should you not cache? L2

Highly dynamic or critical data where staleness is harmful, such as the real-time availability that decides if a booking succeeds (the booking check must hit the database); also responses with user-specific private data unless the key includes the user, and responses to POST or other state-changing requests.

189Redis is in memory, so is the data lost on restart? L1

By default it can be, unless persistence is enabled: RDB takes periodic snapshots, and AOF logs every write. For a cache we can accept loss because the data can be rebuilt from the database; for broker data (Celery queues) loss means pending tasks may disappear, so enable persistence if that matters.

190Is HTTP caching (Cache-Control) the same as Redis caching? L2

No. HTTP caching happens in the browser or intermediate proxies controlled with Cache-Control, ETag and Expires headers, and avoids requests reaching the server at all. Redis caching is server-side: the request arrives, but the app avoids recomputing by reading Redis. They can be used together.

191How do you use two Redis databases or separate Redis roles for broker and cache? L2

Redis has numbered databases (0 to 15 by default), so you can use redis://localhost:6379/0 for the Celery broker and redis://localhost:6379/1 for the cache. This keeps keys separate, so cache.clear() does not delete queued tasks. In production you might run separate instances.

192A flask app returns the current time with `@cache.cached(timeout=120)`. Visits occur at 0 s, 150 s and 210 s. What times are shown? L2

The first visit computes and shows the time at 0 s. The second at 150 s is after the 120 s expiry, so it is a miss: it recomputes and shows the time at about 150 s and the new entry lasts until 270 s. The third at 210 s is within that window, so it shows the time from 150 s again. So the displayed times are 0, 150 and 150 seconds.

193Which statements about Redis are true? Is it a message broker? L1

Redis can act as a message broker because its lists and publish/subscribe features can carry messages, which is how Celery uses it. It is also a cache and a data store with TTLs. It is not a replacement for a relational database for complex joins, and it is not "only" a cache.

194What is Celery and why do you need it? L1

Celery is a distributed task queue for Python. Your Flask app (the producer) puts a task message on a broker such as Redis, and a separate worker process picks it up and runs it in the background. We need it for slow work, such as exporting a CSV, sending emails or building reports, so that the HTTP request returns immediately and does not time out.

195What are the components of a Celery setup? L1

The application that defines tasks and sends them, the broker (Redis or RabbitMQ) that holds the queue of messages, one or more workers that execute tasks, an optional result backend (such as Redis) that stores results and states, and Celery Beat, a scheduler that sends periodic tasks. All of these run as separate processes.

196Show a minimal Celery setup with Flask and one task. L2
from celery import Celery

celery = Celery("app", broker="redis://localhost:6379/0",
                backend="redis://localhost:6379/0")

@celery.task
def add(x, y):
    return x + y
celery -A app.celery worker -l info

In the Flask view call result = add.delay(2, 3) and return result.id. The -A argument points to the module and the Celery object.

197What is the difference between a scheduled task and an async (on-demand) task? L1

An async task is triggered by a user or code event, such as the user clicking "Export CSV", through .delay(). A scheduled (periodic) task is triggered by time, like the daily reminder at 6 PM or the monthly report on the 1st, by Celery Beat. Both are executed by the same workers.

198What does Celery Beat do? L1

Beat is a scheduler process that reads a schedule (crontab or interval) and sends the due tasks to the broker at the right time. It does not execute the tasks; a worker must be running too. So for periodic jobs you start the worker and Beat as two processes (or one with -B in development).

199How do you schedule a daily reminder at 6 PM IST and a monthly report on the 1st? L2
from celery.schedules import crontab
celery.conf.timezone = "Asia/Kolkata"
celery.conf.beat_schedule = {
    "daily-reminder": {"task": "tasks.send_reminders",
                       "schedule": crontab(hour=18, minute=0)},
    "monthly-report": {"task": "tasks.monthly_report",
                       "schedule": crontab(day_of_month=1, hour=8, minute=0)},
}
celery -A app.celery beat -l info

Set the timezone explicitly, otherwise the schedule uses UTC.

200How does the frontend know an async CSV export is finished? L2

The export endpoint starts the task and returns the task id immediately (202). The frontend polls a status endpoint that uses AsyncResult(task_id).state, and when it is SUCCESS it shows a download link, which hits a download endpoint that serves the file. Alternatives are SSE or a notification by email.

@app.get("/api/export/<task_id>")
def status(task_id):
    r = celery.AsyncResult(task_id)
    return {"state": r.state}
201What are the Celery task states? L1

PENDING (waiting or unknown), STARTED (if enabled), SUCCESS, FAILURE, RETRY and REVOKED. Note that PENDING is also reported for an unknown task id. The state is stored in the result backend.

202Why do tasks sometimes fail with "working outside of application context", and how do you fix it? L2

The task runs in the worker process, outside a Flask request, so db, current_app and config are not available automatically. Create the Flask app and push an application context inside the task, or subclass celery.Task with a context wrapper.

class ContextTask(celery.Task):
    def __call__(self, *a, **kw):
        with app.app_context():
            return self.run(*a, **kw)
celery.Task = ContextTask
203How do you handle failures and retries in a task? L2

Use bind=True and call self.retry on a recoverable error, or autoretry_for.

@celery.task(bind=True, max_retries=3, default_retry_delay=60)
def send_mail(self, to):
    try:
        smtp_send(to)
    except ConnectionError as e:
        raise self.retry(exc=e)

Because a task can run more than once, it should be idempotent, so that a retry does not send duplicates or double-update.

204What is the difference between `.delay()` and `.apply_async()`? L2

delay(*args, **kwargs) is a shortcut for apply_async(args, kwargs). apply_async accepts options like countdown=60 (run after 60 seconds), eta, expires, queue and retry. Both return an AsyncResult immediately without waiting for the task.

205Why can't you call `result.get()` inside a Flask view for the CSV export? L2

get() blocks until the task finishes, so the request would hang and time out, which defeats the purpose of a background job. Return the id immediately and let the client poll or be notified.

206Why Redis as a broker, and what are the alternatives? L1

Redis is easy to install and already used as a cache, so the project needs one service for both. RabbitMQ is a dedicated message broker with stronger delivery guarantees and routing features. For a course project Redis is simple and sufficient.

207Why do many guides use WSL for Celery and Redis on Windows? L1

Redis has no official Windows build, and Celery 4 and later dropped official Windows support, as its default prefork pool does not work reliably there. WSL provides a Linux environment where both run normally. (Celery can be run on Windows with --pool=solo, only for development.)

208Which situations are best suited for Celery tasks? L1

Long-running or heavy work that the user should not wait for: report generation, data analysis, sending bulk emails, image processing, calling slow external services, and scheduled jobs. It is not needed for quick operations like a simple database read.

209Arrange these steps for a heavy analysis requested by millions of users: user request, return a task id, put the task in the queue, worker processes it, store the result, client fetches the result. L2

The client sends the request; the API validates it and puts a task on the broker queue; the API immediately returns a task id (HTTP 202); a worker takes the task and runs it; the result is stored in the result backend or database; the client polls (or is notified) and fetches the result. The key point is the API never blocks on the heavy work, and you scale by adding workers.

210If the producer is faster than the consumer in a message queue, what happens first? L2

Messages accumulate in the queue, so the queue grows (and latency increases). You fix it by adding more consumers (workers), speeding up the task, or limiting the producer. The broker's memory can eventually run out if the backlog keeps growing.

211Compare 1000 users getting a report in a loop inside one task versus one subtask per user. L2

One task with a loop is simple but runs sequentially in one worker and a failure may stop the rest. One subtask per user, started from a parent via group() or a loop of .delay(), runs in parallel across workers and retries failures individually, at the cost of more messages. For 1000 emails the per-user tasks are usually better; just limit the rate for the SMTP server.

212How do you scale Celery? L2

Run more worker processes or machines pointed at the same broker, and raise the concurrency (-c 4). Use separate queues for different kinds of tasks, so slow reports do not block quick emails. Only one Beat scheduler should run, otherwise periodic tasks are sent twice.

213What is a webhook? L1

A webhook is an HTTP callback: when an event happens, the source system sends an HTTP POST with the event data to a URL that you registered. It is "reverse API" or "don't call us, we'll call you", so you do not need to poll. Examples: payment gateway notifications, or posting a message to Google Chat or Slack with their incoming webhook URL.

214Show a webhook sender and a receiver. L2
# sender: post a reminder to a chat webhook
import requests
requests.post(WEBHOOK_URL, json={"text": "Your parking ends in 10 minutes"})

# receiver: a Flask route
@app.post("/webhook/payment")
def payment_hook():
    event = request.get_json()
    process(event)
    return "", 204

The receiver should respond quickly (do heavy work in Celery) with a 2xx code, otherwise the sender may retry.

215How do you secure a webhook receiver? L2

Use HTTPS, verify a shared secret or an HMAC signature header computed from the raw body, compare with hmac.compare_digest, validate the payload, ignore replays (timestamps or event ids), and make the handler idempotent because senders retry. Never trust a request just because it reaches your URL.

216Webhook vs polling: when is each better? L1

Webhooks are event-driven: low latency, no wasted requests, but the receiver must be publicly reachable and handle retries. Polling is simple and works behind firewalls and when the source offers no webhooks, but it wastes requests and has delay up to the polling interval.

217What are server-sent events (SSE)? L1

SSE is a one-way stream from server to browser over a normal HTTP connection with the text/event-stream content type. The browser uses EventSource, which reconnects automatically and can resume with Last-Event-ID. Each message is a block of text lines ending with a blank line, such as data: hello.

218Show an SSE endpoint in Flask and the client code. L2
import time
from flask import Response

@app.get("/stream")
def stream():
    def gen():
        while True:
            yield f"data: {time.time()}\n\n"
            time.sleep(2)
    return Response(gen(), mimetype="text/event-stream")
const es = new EventSource("/stream");
es.onmessage = e => console.log(e.data);

Each message must end with a blank line. The server needs a threaded or async worker so a stream does not block other requests.

219SSE vs WebSocket vs polling? L1

Polling asks repeatedly and is simple but wasteful. Long polling holds the request open until data is ready or a timeout. SSE is one-way server to client with automatic reconnect over HTTP. WebSocket is a two-way full-duplex channel over a single connection, better for chat and games.

220Why can't EventSource send an Authorization header, and what do you do about it? L2

The standard EventSource API does not allow custom headers. So send the token as a query parameter (use a short-lived, single-purpose token, since URLs can be logged), or use cookies, or use fetch streaming. This detail matters if your SSE endpoint is protected.

221What is the difference between short polling and long polling? L1

In short polling the client sends requests at fixed intervals and the server answers immediately, even with "no news". In long polling the server keeps the request open until it has new data or a timeout, then the client immediately asks again. Long polling reduces empty responses but holds server connections.

222What is pub/sub and how is it different from point-to-point messaging? L1

In publish/subscribe a publisher sends a message to a topic and every subscriber of that topic receives a copy. In point-to-point (a queue) each message is delivered to exactly one consumer, which is how Celery workers share tasks. A message broker can support both.

223What are the benefits of a message broker? L1

It decouples producer and consumer, so they need not be online together, it buffers bursts of work, allows load to be shared across multiple workers, enables retries and routing, and lets you scale parts independently. The cost is extra infrastructure and eventual (not immediate) results.

224How do you send an email from Flask? L1

Use Python's smtplib with email.message.EmailMessage, or Flask-Mail, connecting to an SMTP server.

import smtplib
from email.message import EmailMessage

def send_mail(to, subject, html):
    msg = EmailMessage()
    msg["From"], msg["To"], msg["Subject"] = "app@example.com", to, subject
    msg.set_content("Please view this in an HTML client")
    msg.add_alternative(html, subtype="html")
    with smtplib.SMTP("localhost", 1025) as s:
        s.send_message(msg)

Sending is slow, so call it from a Celery task.

225Why did you use MailHog (or a similar tool) in development? L1

MailHog is a fake SMTP server that accepts all mail and shows it in a web UI (port 8025, SMTP on 1025) so you can see emails without sending real ones and without credentials. In production you would swap the host and port to a real SMTP provider with TLS and authentication details taken from environment variables.

226How would you generate a monthly HTML report and also attach a PDF? L2

A Celery beat task queries the month's data per user, renders an HTML report with a Jinja template, and sends it as the HTML body. For the PDF convert the same HTML using a library like WeasyPrint, pdfkit (wkhtmltopdf) or xhtml2pdf, and attach the bytes with msg.add_attachment(pdf, maintype="application", subtype="pdf", filename="report.pdf").

227How do you scale sending of many emails? L2

Send them from background tasks, one task per recipient (or in batches) so failures are retried individually, and rate-limit to respect the SMTP provider's limits. Reuse the SMTP connection within a batch, and log failures. Avoid sending in the web request.

228Which statements are true for webhooks and SSE? L1

Both are server-initiated notifications, but differ: webhooks are server-to-server HTTP POSTs to a registered URL, while SSE is a long-lived server-to-browser stream. Webhooks need a publicly reachable receiver; SSE uses a normal browser connection and EventSource, and is one-directional. Neither is bidirectional like WebSockets.

229How did you measure and improve the performance of your application? L1

I measured the response time with the browser DevTools Network tab, a simple timer, or a load tool before and after changes, and ran Lighthouse for the frontend. Improvements: Redis caching of expensive endpoints, background jobs for heavy work, database indexes and pagination, lazy-loaded routes, compressed and sized images. Always say what you measured and the numbers.

230How would you demonstrate that caching improves performance? L1

Call the same endpoint twice and compare the times in the Network tab or with curl -w "%{time_total}": the first (miss) is slow and the second (hit) is fast. Also show the key in redis-cli, and show that a database query log or a print statement appears only for the miss. Then change the data and show invalidation or the TTL expiry.

231What is an N+1 query problem and how do you fix it? L2

It is when you run one query to get N parent rows and then one more query for each parent to get its related rows, giving N+1 queries. In SQLAlchemy fix it with eager loading such as joinedload or selectinload, or a single JOIN query. It is a common reason for slow list endpoints.

232What is a database index and what is the cost? L1

An index is an additional data structure (usually a B-tree) that speeds up lookups, filters and sorts on a column, like a book index. The cost is extra storage and slower writes because the index must also be updated. Index columns used often in WHERE, JOIN or ORDER BY.

233Which frontend techniques improve load time? L1

Code splitting and lazy-loaded routes, minified bundles, removing unused libraries, compressing and lazily loading images, gzip or brotli, HTTP caching for static files, a CDN, and avoiding render-blocking resources. These improve metrics measured by Lighthouse like LCP and TBT.

234Which metrics are in the Lighthouse performance score? L1

First Contentful Paint (FCP), Largest Contentful Paint (LCP), Total Blocking Time (TBT), Cumulative Layout Shift (CLS) and Speed Index are the main ones. Things like Time to First Byte, bundle size and image weight influence them indirectly; accessibility or SEO checks don't change the performance score.

235What is vertical vs horizontal scaling? L1

Vertical scaling gives one machine more CPU or RAM; it is simple but has a limit and a single point of failure. Horizontal scaling adds more machines behind a load balancer, which needs stateless application servers and a shared database or cache. Statelessness (token auth, Redis for shared state) makes horizontal scaling possible.

236Why not run the Flask development server in production? L2

It is single-process and not designed for security, concurrency or reliability. Use a WSGI server such as Gunicorn with several workers behind Nginx, which serves static files, terminates TLS and proxies /api to Flask. This also lets many requests run in parallel.

237How would you load-test your API? L2

Use a tool such as Apache Bench (ab -n 1000 -c 20 http://localhost:5000/api/lots), Locust or k6, and watch the response times, the error rate and the requests per second, with and without caching. The goal is to find the bottleneck, so you test one change at a time.

238What does debounce or throttle do for performance on the frontend? L2

They limit how often an expensive handler runs. Debounce waits until the events stop (search while typing), throttle runs at most once per interval (scroll or resize handlers). Both reduce API calls and re-renders.

239Why is a computed property better than a method for expensive calculations in Vue? L1

Computed values are cached based on their reactive dependencies, so they do not recompute on every render unless data changes, while a method call in the template runs on every re-render. For expensive derived data such as a filtered and sorted list, computed saves work.

240Is caching always good? What are its drawbacks? L2

No. Caching adds complexity: stale data, invalidation bugs, memory use, and the risk of serving one user's data to another if keys are wrong. It helps most for data that is read often, expensive to compute, and changes rarely. Measure first, then cache the real bottleneck.

241Briefly describe the architecture of your project. L1

A Vue 2 single-page app (Vue CLI, Vue Router, Vuex) talks to a Flask REST API with token authentication and SQLAlchemy over SQLite. Redis serves as the cache and as the Celery broker; Celery workers run async jobs (CSV export), and Celery Beat triggers the daily reminders and monthly reports sent by email. Role-based access separates the admin and user. I explain this flow end to end with the main files of each part.

242Why did you choose Vue CLI over the CDN? L1

My app has many components and shared state, so I needed single-file components, npm packages (axios, vue-router, vuex), hot reloading and a production build. The CDN version is simpler, but all templates would be strings or in one HTML page and it does not scale to many components. The trade-off is a build step and Node tooling.

243Why did you use Vuex in your project? L1

Several unrelated components need the same data: the logged-in user, role and token, and some shared lists. Passing them through props and events would be tangled, so a store gives one source of truth. Auth state lives in Vuex (synced to localStorage for refresh), and API calls are in actions. If it were a tiny app, I would not use Vuex.

244Why did you choose token-based authentication rather than sessions? L1

The frontend is a separate SPA, so a token in a header works the same for the SPA, curl or a future mobile app, and the API stays stateless. I used the token issued by Flask-Security (or JWT) and enforced it with @auth_required("token"). Cookies and sessions would need CORS credentials and CSRF handling.

245How did you implement role-based access control? L1

Users have roles (admin, customer and so on) stored in the database with Flask-Security's user-role tables. Each API endpoint is protected with @auth_required("token") and @roles_required("admin") as needed, and customer endpoints filter by current_user.id so users only see their own data. The Vue router guards use the role to hide pages, but the server is the real enforcement.

246Explain how you implemented the daily reminder. L1

A Celery Beat entry with crontab(hour=18, minute=0) triggers a task each day. The task queries users who have not visited or have pending items, builds a message and sends an email (or a Google Chat webhook message) through SMTP. It runs in a worker, uses the application context, and I tested it with a short schedule (every minute) before setting the real time.

247Explain how you implemented the monthly activity report. L1

A Beat task with crontab(day_of_month=1, ...) runs on the first of each month. For each user it aggregates last month's data with SQL (counts, totals, most used items), renders an HTML template, and emails it (optionally with a PDF attachment). Heavy aggregation and email run in the Celery worker so the web server is not affected.

248Explain your CSV export as an async job. L1

The user clicks Export and the API starts a Celery task with .delay() and returns a task id at once. The task queries the data, writes a CSV file and saves it (or emails a link). The frontend polls the status endpoint using the task id and shows a download button when the state is SUCCESS. This avoids request timeouts for large exports.

249How did you use caching in your project, and how do you ensure it isn't stale? L1

I cached read-heavy endpoints, such as the list of lots or the dashboard statistics, in Redis with a TTL chosen from how fast the data changes (60 seconds for availability, longer for static data). Write endpoints invalidate the affected keys, and keys include the user id when the response is user-specific. If asked, I demonstrate it with the timing and redis-cli.

250How is token expiry handled in your app? L1

The server sets a maximum token age; when the token expires the API responds with 401. The frontend's response interceptor catches the 401, clears the store and localStorage, and redirects to login with a message. A short expiry limits the damage of a stolen token; for better usability a refresh token could be added.

251How did you design the database, and why SQLite? L1

Tables with relationships (users, roles, and the domain entities with foreign keys) defined using SQLAlchemy models, with unique constraints and indexes on frequently queried columns. SQLite was required, needs no setup and is fine for a single-user demo; its limits are weak write concurrency and no network access, so for production I would use PostgreSQL.

252What security measures did you take? L1

Hashed and salted passwords, token authentication with expiry, server-side role checks, input validation, ORM queries against SQL injection, Vue's output escaping against XSS, a limited CORS origin list, secrets in environment variables, and no sensitive data in the JWT payload. I would add HTTPS, rate limiting and refresh tokens in production.

253What was the hardest part of the project and what did you learn? L1

Pick a real one, such as getting Celery and Redis to run together, debugging CORS and preflight errors, or keeping cache and database consistent. Describe the symptom, how you found the cause (logs, Network tab, redis-cli), and the fix. A concrete debugging story shows that you built it yourself.

254What would you improve if you had more time? L1

Refresh tokens and token revocation, pagination everywhere, automated tests, Docker Compose for the services, PostgreSQL, rate limiting, better error handling and a CI pipeline. Naming trade-offs honestly shows understanding.

255How do you run the whole project? L1

Start Redis, run the Flask server, start a Celery worker and Celery Beat, start MailHog for emails, and run the Vue dev server (npm run serve) or serve the build. I keep these commands in the README, and I can show each process's log when a task runs.

256How would you scale your project to many users? L1

Use a stateless Flask app behind Nginx with several Gunicorn workers, move from SQLite to PostgreSQL, run multiple Celery workers, and use Redis for caching and shared state. Serve the Vue static build through a CDN. Because token auth is stateless, adding servers is straightforward.

257Which parts of the code did you write yourself and how can you prove it? L1

Be ready to open any file and explain it line by line: the model, an API endpoint, a Vuex action, a Celery task. If you used a library or a template, say so and explain what it does. Examiners can ask you to change something live, so understand your code, not just run it.

258Why did you choose to store the token in localStorage? L1

It is simple, survives refreshes, and works with the Authorization header. I know the risk is XSS, so I escape output, avoid v-html with user data, and use a short expiry. An HttpOnly cookie is an alternative that needs CSRF protection and CORS credentials.

259How do you handle errors and notify the user in your app? L1

The API returns proper status codes and a JSON message, Axios or fetch errors are caught in actions or components, and the UI shows the message (a toast or an alert) and keeps the form data. Specific cases such as 401 trigger a redirect to login, and a failed Celery task is logged and, if it matters, retried.

260Live change: add a "Refresh" button that reloads the list from the API. L2

Add a method and a button in the component.

<button @click="load">Refresh</button>
methods: {
  async load() {
    this.loading = true;
    const res = await fetch("/api/lots");
    this.lots = await res.json();
    this.loading = false;
  }
},
created() { this.load(); }

Reusing the same load method in created avoids duplicate code. Explain that the data assignment triggers a re-render.

261Live change: show a "No items found" message when the list is empty. L2
<p v-if="!loading && lots.length === 0">No items found</p>
<ul v-else>
  <li v-for="l in lots" :key="l.id">{{ l.name }}</li>
</ul>

v-else must follow the v-if element immediately. Use lots.length === 0 because an empty array is truthy.

262Live change: add a search box that filters the displayed list in real time. L2
<input v-model="query" placeholder="Search">
<li v-for="l in filtered" :key="l.id">{{ l.name }}</li>
data() { return { query: "", lots: [] }; },
computed: {
  filtered() {
    const q = this.query.toLowerCase();
    return this.lots.filter(l => l.name.toLowerCase().includes(q));
  }
}

I use computed so it is cached and updates as query changes.

263Live change: protect a new route `/reports` so that only admins can open it. L2

Add meta to the route and check it in the guard.

{ path: "/reports", component: Reports, meta: { requiresAuth: true, role: "admin" } }

The existing beforeEach redirects when the role does not match. Then add @roles_required("admin") on the backend endpoint, because the guard alone is not security.

264Live change: add a new API endpoint that returns the count of items, with admin-only access. L2
@app.get("/api/admin/count")
@auth_required("token")
@roles_required("admin")
def count():
    return {"count": Lot.query.count()}

Then test it in Postman with and without the token and as a non-admin to show 401 and 403. Mention that the decorator order matters: @app.get must be the outermost.

265Live change: change the cache time of an endpoint from 60 seconds to 10 seconds, and show the effect. L2

Change @cache.cached(timeout=60) to timeout=10, restart Flask, and call the endpoint repeatedly. For the first 10 seconds the response is identical (hit); afterwards a recomputed value appears (miss). redis-cli ttl <key> shows the counting down. A shorter TTL gives fresher data but more database work.

266Live change: invalidate the cache when a new item is created. L2

In the POST handler, after the commit, delete the cached key.

db.session.add(lot); db.session.commit()
cache.delete("view//api/lots")   # key used by the cached list view

In Flask-Caching the default key for cached is view/<request.path> (prefix view/), so inspect the key with redis-cli keys "*" or give the decorator an explicit key_prefix="lots" and delete that. Then GET again to show the new item appears.

267Live change: change the daily reminder to run every 2 minutes for a demo. L2

Change the schedule in beat_schedule.

"daily-reminder": {"task": "tasks.send_reminders",
                   "schedule": crontab(minute="*/2")}

Restart Beat (the worker picks the task up automatically), and show the log lines appear every 2 minutes and the mail in MailHog. Using schedule: 120.0 (seconds) also works.

268Live change: add a new Celery task that sends an email to one user and call it from an endpoint. L2
@celery.task
def welcome_mail(to):
    send_mail(to, "Welcome", "<h1>Hello</h1>")

@app.post("/api/welcome/<int:uid>")
@auth_required("token")
def welcome(uid):
    user = User.query.get_or_404(uid)
    welcome_mail.delay(user.email)
    return {"msg": "queued"}, 202

Restart the worker, because it only knows tasks loaded at startup, then show the mail in MailHog.

269Live change: make the Vuex store keep a cart total with a mutation and a getter. L2
state: { items: [] },
mutations: { ADD(state, item) { state.items.push(item); } },
getters: { total: s => s.items.reduce((t, i) => t + i.price, 0) },
this.$store.commit("ADD", { name: "Pen", price: 10 });
// template: {{ $store.getters.total }}

The getter recalculates automatically when items changes.

270Live change: the page shows `undefined` for a nested value on first render. Why and how do you fix it? L2

The data arrives asynchronously, so on the first render user is still null or {} and user.address.city throws or shows nothing. Initialise data with the right shape, or guard the template with v-if="user", or use optional access in a computed property (this.user && this.user.address). In Vue 2 templates the ?. operator is not supported.

271Live change: a button should be disabled while a request is running. L2
<button :disabled="busy" @click="save">Save</button>
async save() {
  this.busy = true;
  try { await axios.post("/api/lots", this.form); }
  finally { this.busy = false; }
}

Using finally makes sure the button is enabled again even when the call fails. It also prevents double submission.

272Live change: add a loading spinner and an error message to a page that loads data. L2

Add loading and error to data, set them in the load method with try/catch/finally, and write <p v-if="loading">Loading...</p><p v-else-if="error" class="err">{{ error }}</p> before the list. Use v-else-if/v-else so only one block displays at a time.

273Live change: the Vue app calls the API and the browser reports a CORS error. Fix it on the backend. L2

Install Flask-CORS and enable it for the frontend origin and allow the needed headers.

from flask_cors import CORS
CORS(app, resources={r"/api/*": {"origins": "http://localhost:8080"}},
     allow_headers=["Content-Type", "Authentication-Token"])

Restart Flask and check in the Network tab that the response has Access-Control-Allow-Origin. If the origin or port is different, the header will not match.

274Live change: show different navbar links for admin and normal users. L2

Read the role from the store and use v-if.

<router-link v-if="role === 'admin'" to="/admin">Admin</router-link>
<router-link v-if="token" to="/profile">Profile</router-link>
computed: { ...mapState(["role", "token"]) }

Mention that this only hides links; the API must still check the role.

275Live change: add pagination (page number and next/previous) to an API endpoint. L2
@app.get("/api/lots")
def lots():
    page = request.args.get("page", 1, type=int)
    p = Lot.query.paginate(page=page, per_page=10, error_out=False)
    return {"items": [l.to_dict() for l in p.items], "page": p.page, "pages": p.pages}

In Vue keep a page in data, call /api/lots?page=${this.page} and disable Previous on page 1 and Next on the last page.

276Live change: add a field (for example "phone") to the user model and the API. What else must change? L2

Add the column to the SQLAlchemy model, then update the database: SQLite will not change an existing table with create_all, so use a migration (Flask-Migrate) or recreate the DB in development. Then update the API's input handling and the output fields, the Vue form (v-model), and any validation. Forgetting the database migration is the common mistake.

277Live change: the child must tell the parent when an item is deleted. Show the code. L2
// child
methods: { remove() { this.$emit("deleted", this.item.id); } }
<!-- parent -->
<item-row v-for="i in items" :key="i.id" :item="i" @deleted="onDeleted"></item-row>
onDeleted(id) { this.items = this.items.filter(i => i.id !== id); }

The parent owns the list, so it does the removal, and the child just reports the event.

278Live change: persist the user's selected theme across refreshes. L2

Save it to localStorage when it changes and read it on load.

data() { return { theme: localStorage.getItem("theme") || "light" }; },
watch: { theme(v) { localStorage.setItem("theme", v); } }

Mention that localStorage holds strings and is per-origin, and that this is non-sensitive data.

279Live change: add a watcher that calls the API when the search text changes, with debounce. L2
watch: {
  query() {
    clearTimeout(this.t);
    this.t = setTimeout(this.search, 300);
  }
},
methods: { async search() { /* call /api/search?q=this.query */ } }

Without the debounce each keystroke would send a request. Clear the timer in beforeDestroy.

280Live change: make the token expire after 30 minutes. L2

For Flask-Security set SECURITY_TOKEN_MAX_AGE = 1800 (seconds) in the config; for Flask-JWT-Extended set JWT_ACCESS_TOKEN_EXPIRES = timedelta(minutes=30). Restart the server. After 30 minutes the API returns 401, and the frontend interceptor logs the user out. To demo, use a small number like 30 seconds.

281Live change: make the Celery export task report progress. L2

Use self.update_state with a custom state and meta in a bound task, and read it in the status endpoint.

@celery.task(bind=True)
def export(self):
    for i, row in enumerate(rows):
        write(row)
        self.update_state(state="PROGRESS", meta={"done": i, "total": len(rows)})

The status endpoint returns r.state and r.info, and the frontend shows a progress bar.

282Live change: add a Redis-backed counter for failed logins to block brute force. L2
key = f"fail:{email}"
n = r.incr(key)
r.expire(key, 600)
if n > 5:
    return {"msg": "Too many attempts"}, 429

INCR is atomic, and the expiry resets the window after 10 minutes. Delete the key after a successful login.