Practice Questions
1. Describe the client-server request-response cycle in your own words. What role does HTTP play in this process?
Solution
When a client (such as a web browser) needs data or a resource, it sends an HTTP request to a server. The server processes the request and sends back an HTTP response containing the requested data (or an error). HTTP (Hypertext Transfer Protocol) is the protocol that defines the format and the rules for these requests and responses, so a client and a server can communicate in the same standard way.
Client ─────────────────── Server
│ │
│ ── HTTP Request ───────▶ │
│ │
│ Process Request
│ │
│ ◀── HTTP Response ────── │
│ │
2. What are the four basic HTTP verbs, and which CRUD operation does each correspond to?
Solution
- GET – Read (retrieve a resource or collection of resources)
- POST – Create (create a new resource)
- PUT – Update (update an existing resource)
- DELETE – Delete (remove a resource)
An HTTP request typically includes a verb (method), a path to a resource, headers, and an optional body. An HTTP response includes a status code, headers, and an optional body.
3. What does the fetch() function return, and how do you access the data from its response? Write a code snippet that fetches JSON data from "/api/items" and logs it to the console.
Solution
fetch() returns a Promise. You chain .then() calls to process the response once it resolves. The first .then() typically parses the response body (e.g., as JSON), and the second .then() receives the parsed data.
fetch("/api/items")
.then((response) => response.json())
.then((data) => console.log(data))
.catch((err) => console.log(err));
The .catch() at the end handles any errors that occur during the request or parsing.
4. Explain the purpose of encodeURIComponent(). Given the search term "New York", what would encodeURIComponent("New York") return, and why is this important when constructing URLs?
Solution
encodeURIComponent() encodes special characters in a string so it can be safely included as part of a URL. Spaces, ampersands, question marks, and other characters that have special meaning in URLs are replaced with percent-encoded equivalents.
encodeURIComponent("New York"); // "New%20York"
This matters because a raw space in a URL breaks the request. Without encoding, ?name=New York is malformed. With encoding, it becomes ?name=New%20York, which the server can correctly interpret.
5. Write a function called getUser that uses fetch to request data from "https://api.example.com/users?id=1", extracts the JSON response, and logs the user’s name. Include error handling.
Solution
function getUser() {
fetch("https://api.example.com/users?id=1")
.then((response) => response.json())
.then((data) => {
console.log(data.name);
})
.catch((err) => console.log(err));
}
The .catch() handles network errors or problems parsing the JSON response. This is the usual shape of code that uses the Fetch API. We fetch, we parse the JSON, we take the data we want out of it, and we handle errors at the end.
6. What does event.preventDefault() do? Why is it necessary when handling a form’s submit event in a single-page application?
Solution
event.preventDefault() stops the browser from performing its default action for an event. For a form’s submit event, the default behavior is to reload the page (sending a traditional form submission to the server).
In a single-page application, we want to handle the form data with JavaScript (e.g., to make an API call) without reloading the page. Calling event.preventDefault() prevents the page reload, allowing our JavaScript code to process the form data instead.
function handleSubmit(event) {
event.preventDefault();
const input = document.getElementById("search").value.trim();
// Process input with JavaScript instead of reloading the page
}
7. Write code that registers an event listener on a form element with the id "my-form" so that when the form is submitted, it calls a function named handleSubmit.
Solution
document.getElementById("my-form").addEventListener("submit", handleSubmit);
The addEventListener method takes two arguments: the event type (as a string) and the callback function to invoke when that event fires. When the user submits the form (e.g., by pressing Enter), the browser fires the submit event, which triggers handleSubmit.
8. What is the difference between innerText and innerHTML? When should you use one over the other, and what security concern is associated with innerHTML?
Solution
innerTextsets or gets the plain text content of an element. It does not parse HTML.innerHTMLsets or gets the HTML content of an element. It parses and renders HTML tags and entities.
Use innerText when you are setting plain text. Use innerHTML when the content includes HTML entities (like ℃ for the Celsius symbol) or markup.
Security concern: Never use innerHTML with user-supplied data. Although modern browsers block <script> tags inserted via innerHTML, other markup can still execute code. For example, <img src="x" onerror="alert('hacked')"> will fire the onerror handler. This is a cross-site scripting (XSS) vulnerability. Always use innerText for user-provided content.
9. Consider the following lookup object pattern. What does the ?? operator do in the return statement? What would getStatusText(200) and getStatusText(418) return?
function getStatusText(code) {
const statuses = {
200: "OK",
404: "Not Found",
500: "Internal Server Error",
};
return statuses[code] ?? "Unknown";
}
Solution
The ?? is the nullish coalescing operator. It returns its right-hand operand when the left-hand operand is null or undefined (but not for other falsy values like 0 or "").
getStatusText(200)returns"OK". The key200exists in the object, sostatuses[200]is"OK".getStatusText(418)returns"Unknown". The key418does not exist, sostatuses[418]isundefined, and the??operator returns the fallback"Unknown".
This pattern is a convenient way to map codes or keys to readable descriptions, with a default for anything that is not in the object.
10. You have two functions: fetchUser(id) fetches a user object (which includes a teamId property), and fetchTeam(teamId) fetches team details. Both use fetch and return their results through .then() chains. Write code that fetches a user, then uses the result to fetch their team, and logs the team data. Handle errors with .catch().
Solution
function fetchUser(id) {
return fetch(`/api/users/${id}`).then((response) => response.json());
}
function fetchTeam(teamId) {
return fetch(`/api/teams/${teamId}`).then((response) => response.json());
}
fetchUser(1)
.then((user) => fetchTeam(user.teamId))
.then((team) => console.log(team))
.catch((err) => console.log(err));
Each function returns the promise chain, so the caller can chain .then() calls one after the other. That matters here because the second fetch depends on data from the first. The .catch() at the end handles errors from either fetch call.
11. Compare using an object lookup (like the pattern in Question 9) versus a chain of if/else statements for mapping values. What are the trade-offs of each approach?
Solution
Object lookup:
- Cleaner and more readable when mapping many values
- Easy to add or remove entries without changing control flow
- Separates data from logic
- Can be defined externally or loaded from a configuration
if/else chain:
- More flexible, because each branch can contain different logic, not just return a value
- Better when conditions are ranges or complex expressions (e.g.,
code >= 200 && code < 300) - Can become verbose and hard to maintain with many cases
For a simple one-to-one mapping, like codes to descriptions, an object lookup is usually the better choice. If the conditions involve ranges, comparisons, or different logic in each branch, use if/else.