Full-stack Flashcards

1
Q

What happens when you type a URL into the browser and hit enter?

A
  1. DNS Resolution: The browser checks the domain name (e.g., www.example.com) and queries a DNS server to translate it into an IP address (the location of the web server).
  2. HTTP Request: The browser sends an HTTP request to the web server at the resolved IP address, requesting the specific resource (e.g., a webpage).
  3. Server Response: The server processes the request, retrieves the resource (like an HTML file), and sends it back as an HTTP response.
  4. Rendering: The browser parses the HTML, executes any JavaScript, and renders the page. It may send additional requests for assets like CSS, images, or scripts until the page is fully loaded.
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
2
Q

What’s the difference between monolithic and microservice architectures?

A

Monolithic architecture is a software design where all components of an application are tightly integrated and deployed as a single unit. It’s easier to develop initially but can become complex and harder to scale or maintain as the application grows.

Microservice architecture breaks an application into smaller, independent services that each handle a specific function. These services communicate over a network, allowing for better scalability, flexibility, and easier maintenance, though it introduces complexity in terms of communication and deployment.

The key difference is that monolithic is a single, unified system, while microservices are separate, self-contained services working together.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
3
Q

How do you design a RESTful API, and what makes it RESTful?

A

To design a RESTful API, ensure it is resource-based, where each resource is represented by a URI, and use standard HTTP methods like GET, POST, PUT, and DELETE to perform operations on those resources. The API should be stateless, meaning each request must contain all the information needed to process it.

Additionally, you should use appropriate HTTP status codes to communicate the result of the request. A RESTful API adheres to these principles to be simple, scalable, and easy to maintain.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
4
Q

When would you use GraphQL over REST?

A

You would use GraphQL over REST when you need more flexibility in querying data. With GraphQL, clients can request exactly the data they need, reducing over-fetching or under-fetching issues that are common in REST APIs.

It’s also useful when dealing with complex or nested data structures, as it allows you to fetch multiple related resources in a single request.

Additionally, GraphQL can handle real-time data updates with subscriptions, making it a better choice for dynamic applications.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
5
Q

What are CORS and how do you handle CORS issues?

A

CORS (Cross-Origin Resource Sharing) is a security feature implemented by browsers to prevent malicious websites from making requests to a different domain than the one that served the web page. It determines whether a web page can make requests to a different domain, protocol, or port.

To handle CORS issues, the server must include the appropriate CORS headers in the response, such as:
- Access-Control-Allow-Origin: Specifies which domains are allowed to access the resource.
- Access-Control-Allow-Methods: Defines the allowed HTTP methods (GET, POST, etc.).
- Access-Control-Allow-Headers: Lists the headers that can be sent with the request.

For development purposes, you can also use middleware like CORS in Express.js to automatically handle these headers.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
6
Q

How do you manage authentication state on the frontend securely?

A

To manage authentication state securely on the frontend, you can store tokens (e.g., JWT) in HTTP-only cookies to prevent access via JavaScript, reducing the risk of XSS attacks.

Use localStorage or sessionStorage cautiously, as they can be vulnerable to XSS attacks.

Implement token expiration and refresh mechanisms to ensure session security, and always use HTTPS to protect data in transit.

Additionally, avoid exposing sensitive data in the frontend and rely on backend verification for authentication.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
7
Q

What are the pros and cons of server-side rendering (SSR) vs client-side rendering (CSR)?

A

Server-Side Rendering (SSR) has the following pros and cons:

  • Pros: Faster initial page load since the HTML is pre-rendered on the server, improving SEO because search engines can index the content easily. It also benefits users with slow connections or devices, as the server sends a fully-rendered page.
  • Cons: Can put more load on the server and increase response times, especially under heavy traffic. It may also lead to slower subsequent interactions since the client still needs to fetch and re-render data dynamically.

Client-Side Rendering (CSR) has its own advantages and disadvantages:

  • Pros: Faster interactions after the initial load, as subsequent navigation only requires fetching data. It reduces server load and allows for more dynamic, interactive UIs.
  • Cons: Slower initial load since the client must download, parse, and execute JavaScript. SEO can be challenging unless additional measures (like prerendering) are taken, as search engines may have difficulty indexing content rendered only on the client side.
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
8
Q

How would you implement lazy loading in a single-page application (SPA)?

A

To implement lazy loading in an SPA, you can use code splitting tools like Webpack or React.lazy() to load components only when they are needed, rather than all at once.

For example, in React, you can dynamically import components with React.lazy() and wrap them in a Suspense component to handle loading states. Route-based lazy loading can also be used to load specific components when users navigate to different parts of the app.

This approach reduces initial load time and improves performance by only loading necessary code.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
9
Q

How do you handle environment variables in a frontend app securely?

A

To handle environment variables securely in a frontend app, avoid directly exposing sensitive information like API keys in the client-side code. Use build tools (e.g., Webpack or Vite) to inject environment variables into the build process, but ensure that only non-sensitive information (like URLs) is exposed to the client-side.

Sensitive data should be stored securely on the server, and API calls should be made through a backend that acts as a proxy to keep secrets hidden from the frontend.

Additionally, use HTTPS to encrypt communications between the client and server.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
10
Q

How do you optimize frontend performance for time to first byte (TTFB) and page speed?

A

To optimize frontend performance for Time to First Byte (TTFB) and page speed, focus on reducing server response times and optimizing resource loading:

  1. Improve server performance: Use techniques like caching (e.g., CDNs, HTTP caching headers) and optimizing server-side processing to reduce TTFB. Consider using server-side rendering (SSR) to send a pre-rendered HTML page quickly.
  2. Minify and compress resources: Minify CSS, JavaScript, and HTML files, and enable gzip or Brotli compression to reduce the size of assets sent over the network.
  3. Lazy loading and code splitting: Implement lazy loading for images and other assets, and code splitting for JavaScript to load only the necessary parts of the app initially.
  4. Optimize images and assets: Use modern image formats like WebP, compress images, and implement responsive images (srcset) to ensure assets load quickly without sacrificing quality.

These techniques help reduce both TTFB and overall page load times, enhancing user experience.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
11
Q

How does event-driven architecture work in Node.js?

A

In Node.js, event-driven architecture is based on the Event Loop, which listens for and handles events asynchronously. Events are emitted by EventEmitter objects, and corresponding event listeners (callbacks) are triggered when an event occurs.

This architecture allows Node.js to efficiently handle I/O operations, like file reads or HTTP requests, without blocking the main thread.

As a result, Node.js is well-suited for real-time applications, capable of handling high concurrency with minimal resource usage.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
12
Q

What are the differences between synchronous and asynchronous programming on the backend?

A

In synchronous programming, each operation is executed sequentially, meaning the server waits for one task to complete before moving on to the next. This can lead to slower performance when handling multiple requests, as the server is blocked during I/O operations like database queries or file reads.

In asynchronous programming, operations are executed non-blocking, allowing the server to continue processing other requests while waiting for time-consuming tasks to finish. This approach improves performance and scalability, as the server can handle many tasks concurrently without being blocked, making it ideal for handling I/O-heavy operations like in Node.js.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
13
Q

How do you handle error logging and monitoring in a backend service?

A

To handle error logging and monitoring in a backend service, you can use structured logging tools like Winston or Bunyan to capture detailed error logs with relevant metadata (e.g., timestamps, request IDs). Integrating a centralized logging system like ELK Stack (Elasticsearch, Logstash, Kibana) or Graylog helps aggregate logs from multiple services for better analysis.

For monitoring, tools like Prometheus or Datadog can be used to track metrics, alert on failures, and monitor system health. Additionally, setting up automatic error reporting with services like Sentry can provide real-time alerts and detailed error tracking, allowing you to address issues quickly.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
14
Q

What are middleware functions in Express, and how do they work?

A

Middleware functions in Express are functions that have access to the request, response, and the next middleware function in the request-response cycle. They are used to process incoming requests before they reach the route handler or to modify the response before sending it back to the client.

Middleware can perform tasks such as logging requests, parsing request bodies, handling authentication, or managing error handling. To use a middleware, you define it and call the next() function to pass control to the next middleware or route handler. You can apply middleware globally or to specific routes.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
15
Q

How would you implement rate limiting in an API?

A

To implement rate limiting in an API, you can use middleware to restrict the number of requests a user can make within a specific time frame.

Libraries like express-rate-limit in Express.js help set limits on requests per IP, for example, allowing 100 requests per hour.

For scalable applications, you can use Redis to store and track request counts across multiple instances, enabling distributed rate limiting. This approach helps prevent abuse, reduces server load, and ensures fair access to the API.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
16
Q

When should you use SQL vs NoSQL databases?

A

You should use SQL databases when your data is structured, relational, and requires complex queries, joins, or transactions. SQL databases are ideal for applications where data integrity, ACID (Atomicity, Consistency, Isolation, Durability) properties, and consistency are critical, such as in banking systems or e-commerce platforms.

NoSQL databases are better suited for applications with unstructured or semi-structured data, where flexibility and scalability are needed. They are ideal when dealing with large volumes of rapidly changing data, such as in social media platforms, content management systems, or real-time analytics, where high availability and horizontal scaling are more important than strict consistency.

17
Q

How would you model a many-to-many relationship in both SQL and NoSQL?

A

In SQL, a many-to-many relationship is modeled using a junction table, which contains foreign keys referencing the primary keys of the two related tables.

For example, in a students and courses relationship, a student_courses table would link student_id and course_id as foreign keys. In NoSQL, such as MongoDB, you typically store references to related documents as arrays within a document, like having an array of course IDs in the student document.

Alternatively, documents can be embedded if the data is small and non-complex. Graph databases like Neo4j handle many-to-many relationships with nodes and edges, making it easy to traverse complex relationships between entities.

18
Q

What is indexing, and how does it impact query performance?

A

Indexing is a technique used to optimize the speed of data retrieval operations on a database. An index is a data structure that stores a sorted list of values from one or more columns of a table, along with pointers to the corresponding rows in the database. By using indexes, databases can quickly locate data without scanning the entire table, which greatly improves the performance of SELECT queries, especially on large datasets.

However, indexing also has trade-offs: while it speeds up read operations, it can slow down write operations (INSERT, UPDATE, DELETE) because the index must be updated whenever the data changes. Additionally, indexes consume extra disk space. Therefore, careful planning is needed to choose the right columns to index based on query patterns.

19
Q

How would you prevent SQL injection or NoSQL injection attacks?

A

To prevent SQL injection in relational databases, always use parameterized queries or prepared statements instead of concatenating user input directly into SQL queries. For example, in languages like JavaScript or Python, use libraries that support parameterized queries (e.g., pg for PostgreSQL or mysql2 for MySQL) to safely handle user input. This ensures that input is treated as data, not executable code.

For NoSQL injection (commonly in document-based databases like MongoDB), ensure that queries are constructed safely by avoiding direct user input in query objects. Use validation and sanitization techniques to filter and escape user input, and apply proper access controls to limit which queries users can execute. Additionally, avoid using eval() or similar functions, which could execute arbitrary JavaScript code. Properly validating and sanitizing input for both types of injection attacks is crucial to application security.

20
Q

How do transactions work in relational databases?

A

In relational databases, a transaction is a sequence of operations that are executed as a single unit, ensuring data integrity and consistency. It follows the ACID properties: Atomicity ensures all operations succeed or fail together, Consistency guarantees the database remains in a valid state, Isolation prevents interference between concurrent transactions, and Durability ensures changes are permanent once committed. Transactions are controlled with commands like BEGIN TRANSACTION, COMMIT, and ROLLBACK to manage their execution.

This ensures that the database maintains its integrity, even in the case of errors or system failures.

21
Q

What’s the difference between session-based and token-based authentication?

A

Session-based authentication stores user authentication information on the server side. After the user logs in, the server creates a session, stores session data (such as user information), and sends a session ID to the client in a cookie. Each subsequent request includes the session ID, allowing the server to authenticate the user by checking the session data stored on the server.

Token-based authentication, on the other hand, uses tokens (commonly JWT) that contain user information, which are signed and sent to the client. The client stores the token (usually in localStorage or a cookie) and includes it in the Authorization header for subsequent requests. The server doesn’t store any session data but verifies the token’s validity and authenticity using a secret key.

The key difference is that session-based relies on server-side storage, while token-based is stateless and stores the user data directly within the token, making it more scalable for distributed systems.

22
Q

How do JWTs work and how do you securely store and validate them?

A

JWTs (JSON Web Tokens) are compact tokens that securely transmit information between parties in three parts: a header (specifying the signing algorithm), a payload (containing the claims or data), and a signature (created by signing the header and payload with a secret key or private key). The server generates the token after successful authentication and sends it to the client, which stores it (typically in HTTP-only cookies or localStorage).

To securely store JWTs, it’s best to use HTTP-only cookies to mitigate XSS attacks, though localStorage can be used with caution. On each request, the server validates the token by checking its signature and expiration time, ensuring it hasn’t been tampered with.

Using HTTPS for secure transmission and securely managing the secret key is crucial for preventing unauthorized access or interception.

23
Q

What are common attack vectors in full-stack apps (XSS, CSRF, SSRF), and how do you defend against them?

A

Common attack vectors in full-stack apps include XSS (Cross-Site Scripting), CSRF (Cross-Site Request Forgery), and SSRF (Server-Side Request Forgery). Here’s how to defend against them:

  1. XSS (Cross-Site Scripting): XSS occurs when an attacker injects malicious scripts into web pages, which are then executed in the browser. To defend against XSS, always sanitize and escape user inputs, use Content Security Policy (CSP), and avoid using functions like eval() or innerHTML. Additionally, ensure that any data rendered to the page is properly escaped to prevent scripts from running.
  2. CSRF (Cross-Site Request Forgery): CSRF tricks users into performing actions they didn’t intend, such as changing account settings. To protect against CSRF, use anti-CSRF tokens in forms and HTTP requests, ensuring that each request is validated for a unique token that cannot be forged. Also, ensure that sensitive requests are only made using HTTP POST and SameSite cookies to restrict cross-origin requests.
  3. SSRF (Server-Side Request Forgery): SSRF occurs when an attacker sends a request from the server to an internal or external resource, bypassing security controls. To mitigate SSRF, always validate and sanitize user inputs, particularly URLs and IP addresses, and restrict the server from making requests to internal resources unless necessary. Implementing network-level firewalls and whitelisting allowed destinations can also prevent unauthorized access.

By employing these defenses, you can significantly reduce the risk of these common security threats in full-stack applications.

24
Q

How would you implement OAuth2 in a web app?

A

To implement OAuth2 in a web app, start by registering your app with an OAuth2 provider to obtain a client ID and client secret. Redirect the user to the provider’s authorization endpoint, where they can authenticate and grant permission, and then handle the redirect to obtain an authorization code.

Exchange the authorization code for an access token by sending a request to the provider’s token endpoint, using the client credentials.

Store the access token securely and use it to make authenticated requests on behalf of the user, handling token expiration with refresh tokens if necessary.

25
How do you hash and salt passwords securely?
To securely **hash and salt passwords**, follow these steps: 1. **Salting**: A **salt** is a random string of data added to the password before hashing. It ensures that even if two users have the same password, their hashes will be different. Generate a unique salt for each password and combine it with the user's password before hashing. 2. **Hashing**: Use a **secure hashing algorithm** like **bcrypt**, **argon2**, or **PBKDF2**. These algorithms are designed to be slow, making it computationally expensive for attackers to brute force hashed passwords. Avoid using weak or outdated algorithms like MD5 or SHA1. 3. **Storing**: Store both the **hashed password** and **salt** in the database (bcrypt and argon2 handle both together, so you don't need to store the salt separately). When a user logs in, you combine the provided password with the stored salt, hash it again, and compare it to the stored hash. By using salt with a strong, slow hashing algorithm, you can significantly increase the security of stored passwords and protect against attacks like rainbow tables or brute-force attacks.
26
How would you set up CI/CD for a full-stack application?
To set up CI/CD for a full-stack application, first ensure that your project is stored in a version control system like Git (on platforms such as GitHub, GitLab, or Bitbucket). Choose a CI tool like GitHub Actions, Jenkins, GitLab CI, or CircleCI, and configure it to run automated tests (unit, integration, or end-to-end) whenever code changes are pushed to the repository. You can define a .yml configuration file to specify the pipeline steps, such as which tests to run and when to trigger the build. For Continuous Deployment (CD), configure the CI tool to deploy your app to various environments, like staging or production, using services such as Heroku, AWS, or Netlify. It's important to securely manage sensitive information, such as API keys or database credentials, using environment variables within your CI/CD pipeline. By automating the testing, building, and deployment processes, CI/CD improves the speed, consistency, and reliability of delivering updates to a full-stack application.
27
What’s the difference between Docker containers and virtual machines?
Docker containers are lightweight and share the host operating system's kernel, making them faster to start and more resource-efficient compared to virtual machines (VMs), which run a full operating system on top of a hypervisor. Containers provide process-level isolation, while VMs offer full hardware-level isolation, with each VM running its own operating system. Containers are typically used for microservices and scalable applications, as they are easier to deploy and manage. In contrast, VMs are better suited for running different operating systems or legacy applications that require full system isolation.
28
How do you monitor and scale a backend service in production?
To **monitor** a backend service in production, you should use tools like **Prometheus**, **Grafana**, or **Datadog** to track key metrics such as CPU usage, memory consumption, response times, and error rates. Set up **log aggregation** with tools like **ELK stack** (Elasticsearch, Logstash, Kibana) or **Splunk** to collect and analyze logs for identifying issues. To **scale** the service, employ **horizontal scaling** (adding more instances) or **vertical scaling** (increasing resources for existing instances), depending on the demand. Implement **auto-scaling** policies in cloud platforms (e.g., AWS, Azure) to dynamically adjust resources based on real-time metrics. Finally, ensure the use of **load balancers** to evenly distribute traffic across multiple instances and avoid service bottlenecks.
29
What’s the purpose of reverse proxies like Nginx or HAProxy in production?
Reverse proxies like Nginx and HAProxy are used in production environments to improve performance, security, and reliability. They act as intermediaries between client requests and backend services, forwarding requests to the appropriate server while hiding the identity of the backend infrastructure. They help with load balancing, distributing incoming traffic across multiple backend servers to ensure even resource utilization and prevent server overload. Reverse proxies also enhance security by acting as a barrier, preventing direct access to backend services and mitigating attacks like DDoS. Additionally, they offer SSL termination, caching, and request routing to optimize performance and reduce the load on backend systems.
30
How would you structure environment configurations (dev, staging, prod) across the stack?
To structure **environment configurations** (dev, staging, prod) across the stack, follow these best practices: 1. **Separate Configuration Files**: Maintain different configuration files for each environment (e.g., `config.dev.js`, `config.staging.js`, `config.prod.js`). Each file contains settings specific to that environment, such as database URLs, API keys, and logging levels. 2. **Environment Variables**: Use **environment variables** for sensitive data like API keys, database credentials, and secret tokens, which can be different across environments. Store these variables in a `.env` file or use a configuration management tool (e.g., AWS Secrets Manager, HashiCorp Vault). 3. **Environment-Specific Code**: In your codebase, conditionally load configurations based on the environment using environment variables or flags. This allows the app to automatically adjust settings (e.g., logging level or feature toggles) depending on whether it's running in development, staging, or production. 4. **Automated Deployment**: For each environment, create separate deployment pipelines in your CI/CD setup, ensuring that the appropriate environment configuration is loaded and the application is deployed with the correct settings. Tools like **Docker** or **Kubernetes** can help manage these environments more efficiently by providing isolated containers for each. By organizing configurations in this manner, you can ensure your application behaves correctly in different environments while keeping sensitive information secure and the deployment process streamlined.