0% found this document useful (0 votes)
2 views74 pages

F Insight

The document outlines the features and functionalities of the FINSIGHT TCS application, including user authentication, portfolio management, stock data analysis, and system automation. It details the processes for user registration, login, and portfolio insights, as well as the technical setup for the application, including middleware, route handling, and debugging tools. Additionally, it explains the importance of security measures such as password hashing and input validation in user registration and login processes.

Uploaded by

nk2332001
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
2 views74 pages

F Insight

The document outlines the features and functionalities of the FINSIGHT TCS application, including user authentication, portfolio management, stock data analysis, and system automation. It details the processes for user registration, login, and portfolio insights, as well as the technical setup for the application, including middleware, route handling, and debugging tools. Additionally, it explains the importance of security measures such as password hashing and input validation in user registration and login processes.

Uploaded by

nk2332001
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd

FINSIGHT TCS

Authentication & User Management


1. User Registration (Sign Up): Creating a new account with secure password
hashing.

2. User Login: Authenticating users and issuing JWT sessions.

3. Protected Routes: Preventing unauthorized access to dashboard pages.

Portfolio Management
1. Add Transaction: Adding a new stock holding (Ticker, Quantity, Price,
Date).

2. Smart Holding Updates: Automatically averaging the buy price if you add
more of a stock you already own.

3. Portfolio Dashboard: Viewing a unified table of all holdings with live P&L
and current value.

4. Net Worth History: A line chart visualizing portfolio value changes over
time (Day/Week/Month/Year).

5. Portfolio Insights: Visual breakdowns of your portfolio by Sector (e.g.,


Tech vs. Finance) and Market Cap (Large vs. Small Cap).

6. Performance Metrics: Automatic identification of "Best Performer" and


"Worst Performer" stocks.

7. Portfolio Health Meter: A speedometer gauge showing overall portfolio


return percentage.

Stock Data & Analysis


1. Stock Search: A global search bar with auto-suggestions to find stocks.

2. Live Price Tracking: Fetching real-time stock prices from Yahoo


Finance/RapidAPI.

3. AI Stock Insights (Gemini): Generating detailed reports (Fundamental,


Technical, Future Outlook) for any specific stock.

FINSIGHT TCS 1
4. News Feed: Aggregating relevant news articles for the user's specific
holdings.

5. Sentiment Analysis: AI-based scoring of news headlines to determine if


market sentiment is Positive, Negative, or Neutral.

System & Automation


1. Daily Cron Job: An automated background task that runs every night to
update stock prices and history.

2. Caching System: Storing AI analysis and stock data temporarily to reduce


API costs and speed up loading.

Which feature would you like to breakdown next? (We have already done #1
and #2).

Application Entry Point


The Flow (What happens when you run npm start )

1. Environment Setup:

Action: [Link]() loads your secrets (API Keys, DB URL) from the
.env file into [Link] .

Cron Job Registration: import "./jobs/[Link]" runs immediately. This


tells the server: "Wake up every day at 9:30 PM to run this job." If
you removed this line, your auto-updates would never happen.

2. Database Connection:

Action: connectDB() is called. The server waits here until it


successfully connects to MongoDB. If this fails, the app usually
crashes (as it should).

3. Middleware Pipeline (The Gatekeepers):

: Allows your Frontend (running on port 5173) to talk to


[Link](cors())

this Backend (running on port 5050). Without this, the browser


would block requests due to security rules.

FINSIGHT TCS 2
: Tells Express to parse incoming data as JSON. Without
[Link]()

this, [Link] would be undefined in all your controllers.

4. Route Mounting (Traffic Control):

Action: The app defines "Base URLs".

means any request starting with /api/users is


[Link]("/api/users", userRoutes)

sent to the userRoutes file to be handled. This keeps [Link] clean


instead of having 50 routes in one file.

5. Server Start:

Action: [Link](PORT) opens the port (5050) and starts accepting


traffic.

Interview Questions for [Link]


1. What is the purpose of cors middleware?

Answer: Cross-Origin Resource Sharing. By default, browsers block


frontend code on one port (5173) from calling an API on another
(5050). cors() adds specific headers to the response telling the
browser "It's okay, allow this."

2. Why is [Link]() at the very top?

Answer: Because other imports (like connectDB ) might rely on


environment variables (like MONGODB_URI ). If we load the config after
the imports, those variables would be undefined, and the
connection would fail.

3. Why do you import the cron job file ( import "./jobs..." ) without assigning it
to a variable?

Answer: This is a "Side-Effect Import". I don't need to call a function


from that file; I just need the code inside that file (the [Link]
function) to execute once when the server starts.

2. The "Dev Button" ( server/routes/[Link] )


This is a hidden tool for developers.

The Flow

FINSIGHT TCS 3
The Problem: You wrote a Cron Job that runs at 9:30 PM. You want to
test if it works right now (at 10:00 AM). You don't want to change the
system clock or wait 12 hours.

The Solution: You created a route POST /api/test/trigger-price-update .

Action: When you hit this endpoint (via Postman), it imports and runs
the triggerManualUpdate function—the exact same logic the Cron Job uses.

Result: It forces an immediate update of all stock prices and returns a


JSON report ( { message: "Price update completed" } ).

Interview Questions for [Link]


1. How do you test scheduled tasks without waiting for the schedule?

Answer: I abstract the logic into a separate function


( triggerManualUpdate ) and expose it via a temporary API endpoint. This
allows me to trigger the job on-demand for testing.

2. Should this file exist in Production?

Answer: Ideally, no. In a real company, I would wrap this route in an


if ([Link].NODE_ENV === 'development') check, or protect it with an

Admin-only middleware, so random users can't spam my server


with heavy update jobs.

3. The Debug Script ( server/[Link] )


This is a "Scratchpad" script. It is not connected to the main server.

The Flow
Usage: You run this file directly from the terminal: node server/debug-

[Link] .

Action:

1. Connects to the database manually.

2. Calls your yahooFinanceService specifically for "AAPL".

3. [Link] s the raw metadata (Market Cap, P/E) to the terminal.

4. Exits ( [Link](0) ).

FINSIGHT TCS 4
Purpose: This was likely used when you were building the "Portfolio
Enhancement" feature. You needed to see exactly what data Yahoo was
sending back without running the whole frontend and adding a stock.

Interview Questions for Debug Scripts


1. How do you debug a specific service function in isolation?

Answer: I create small, standalone scripts like [Link] that


import the service, connect to the DB, and run the function with test
data. This isolates the logic from the rest of the HTTP/Express layer.

Why were these not covered earlier?


I initially prioritized the Functional Requirements (Features) over the Non-
Functional Requirements (Infrastructure).

1. User-Centric vs. Dev-Centric: The previous 95% were things the User
experiences (Login, Charts, Search). These last 3 files are things only
the Developer sees.

2. Business Logic vs. Boilerplate: The core value of your project ("AI
Insights", "Sentiment Analysis") lies in the controllers and services.
is largely standard "Boilerplate" code that looks the same in
[Link]

almost every Express app.

3. Flow vs. Setup: We were tracing the flow of data (User -> API -> DB).
These files are the setup that happens before any data flows.

Now, you are truly 100% covered. You understand the setup, the features,
the automation, and the debugging tools.

User Authentication & Security

The Registration Flow (Step-by-Step)

Step 1: User Input (Frontend)

File: client/src/pages/[Link]

Action: The user types their Name, Email, and Password. React
stores this data in the component's state ( formData ).

FINSIGHT TCS 5
Trigger: When the user clicks "Create Account", the handleSubmit

function executes.

Network Call: It sends a POST request via Axios to ${API}/users/register

with the form body.

Step 2: Route Handling (Backend)

File: server/routes/[Link]

Action: The Express router intercepts the POST /register request.

Routing: It forwards the request specifically to the registerUser

function located in the controller.

Step 3: Validation & Business Logic (Controller)

File: server/controllers/[Link]

Action: The registerUser function receives [Link] .

Validation: It checks if email , password , or name are missing. If so, it


stops and sends a 400 Bad Request .

Duplicate Check: It calls findUserByEmail (from the service layer). If a


user with that email exists, it stops and returns 400 ("User already
exists").

Step 4: Database Preparation (Service Layer)

File: server/services/[Link]

Action: If the user is new, the controller calls createUser .

Logic: This service instantiates a new Mongoose User object with


the data. It does not hash the password here; it simply prepares the
object to be saved.

Step 5: Automatic Security Hook (Model Middleware)

File: server/config/models/[Link]

Action: This is the most critical step. Before MongoDB saves the
data, the [Link]('save') middleware runs.

The Logic:

1. It checks if (![Link]("password")) . (Is this a new or changed


password?)

FINSIGHT TCS 6
2. It uses Bcrypt to hash the plain text password (e.g.,
"password123" becomes $2b$10$XyZ... ).

3. It replaces the plain password with this hash on the user object.

Result: The database never sees or stores the real password.

Step 6: Session Creation (JWT)

File: server/controllers/[Link]

Action: Once the user is successfully saved, the controller calls


[Link]() .

Result: A JSON Web Token is created, signing the user's new _id

with the server's secret key.

Step 7: Final Response & Auto-Login

Files: server/controllers/[Link] → client/src/pages/[Link]

Backend: Responds with status 201 Created and the JSON object {

user, token } .

Frontend: Receives the token and immediately saves it to localStorage .


This logs the user in instantly without requiring them to go to the
login page first.

2. Why We Used This (Technical Decisions)


Why use bcrypt instead of standard Encryption?

Reason: Encryption is reversible (if you have the key, you can get
the password back). Hashing is one-way (you can never get the
original text back). If a hacker steals your database, they cannot
read the passwords, protecting your users.

Why put the hashing logic in pre('save') and not in the Controller?

Reason: DRY (Don't Repeat Yourself). If you hash the password in


the controller, you have to remember to do it in the "Register"
controller, the "Reset Password" controller, and the "Admin Create
User" controller. By putting it in the Model's middleware, the
password is automatically hashed anytime it is saved, no matter
where the request came from.

Why use a Service Layer ( [Link] )?

FINSIGHT TCS 7
Reason: Separation of Concerns. The Controller should only care
about HTTP stuff (Requests, Responses, Status Codes). The
Service should care about Business Logic (Database queries). This
makes the code cleaner and easier to test.

Why return a Token immediately after Sign Up?

Reason: User Experience (UX). It is annoying for a user to create


an account and then be told "Success! Now go log in." By sending
the token immediately, we reduce friction and let them start using
the app instantly.

Why POST and not PUT ?

Reason: Semantics. POST is used to create a new resource that


didn't exist before. PUT is typically used to update or replace an
existing resource.

3. Important Interview Questions (User Registration)


1. What is the difference between Hashing and Encryption? Which one
did you use for passwords and why?

Answer: Encryption is two-way (reversible with a key); Hashing is


one-way (irreversible). I used Hashing (Bcrypt) so that even if the
database is compromised, the attackers cannot retrieve the original
passwords.

2. What is a "Salt" in Bcrypt and why is it necessary?

Answer: A salt is random data added to the password before


hashing. It ensures that if two users have the same password (e.g.,
"123456"), their hashes in the database look completely different,
preventing "Rainbow Table" attacks.

3. Explain the HTTP status code 201 . When would you use it over 200 ?

Answer: 200 is a generic "OK". 201 specifically means "Created". I


use 201 for the registration endpoint to indicate that a new resource
(User) was successfully created in the database.

4. What is the purpose of the next() function in the Mongoose pre('save')

middleware?

FINSIGHT TCS 8
Answer: Middleware functions in [Link]/Mongoose run in a
sequence. next() tells Mongoose "I am done with my logic, you can
move on to the next step (which is actually saving to the DB)." If you
forget to call it, the request will hang forever.

5. Your schema has email: { unique: true } . How does MongoDB handle this?

Answer: MongoDB creates a unique index on the email field. If I try


to insert a document with a duplicate email, MongoDB itself throws
a generic duplicate key error (code 11000), which allows us to
guarantee data integrity at the database level.

6. Why do you use [Link]() in the frontend handleSubmit function?

Answer: By default, HTML forms reload the entire page when


submitted. In a Single Page Application (React), we want to prevent
this reload so we can handle the data submission via JavaScript
(Axios) seamlessly without resetting the app state.

7. What is "Input Validation" and where did you implement it?

Answer: Input validation ensures the data meets our requirements


before processing. I implemented it in the Controller (checking for
missing fields) and in the Mongoose Schema (checking for minlength
on passwords).

8. If the database operation fails (e.g., database is down), how does


your controller handle it?

Answer: I wrapped the logic in a try/catch block. If the service throws


an error, the catch block catches it and sends a 500 Internal Server Error
response with the error message.

9. Why separate the User model from the User controller?

Answer: This follows the MVC (Model-View-Controller) pattern. The


Model defines the data structure and rules. The Controller handles
the user's request. Mixing them would make the code messy, hard
to read, and difficult to maintain.

10. Explain the async/await keywords used in your registerUser function.

Answer: Database operations (like save() or findOne() ) take time to


complete. async/await allows us to write asynchronous code that
looks synchronous. It pauses the function execution at the await line

FINSIGHT TCS 9
until the database responds, ensuring we don't proceed with
undefined data.

Login

Step 1: User Input (Frontend)

File: client/src/pages/[Link]

Action: The user enters their Email and Password. React tracks this
input using useState ( formData ).

Trigger: When the user clicks "Login", handleSubmit is called.

Network Call: It sends a POST request via Axios to ${API}/users/login

with the email and password.

Step 2: Route Handling (Backend)

File: server/routes/[Link]

Action: The Express router intercepts the POST /login request.

Routing: It forwards the request to the loginUser function in the


controller.

Step 3: User Lookup (Controller)

File: server/controllers/[Link]

Action: The loginUser function executes.

Validation: Checks if email and password are present.

Database Query: It calls findUserByEmail(email) (from the service layer)


to find the user document in MongoDB.

Check: If no user is found, it immediately returns a 404 Not Found error


("User not found").

Step 4: Password Verification (Model Method)

File: server/config/models/[Link]

Action: If the user exists, the controller calls the custom method
[Link](password) .

The Logic:

1. It uses Bcrypt ( [Link] ).

FINSIGHT TCS 10
2. It takes the plain text password from the request (e.g.,
"password123").

3. It compares it against the encrypted hash stored in the


database (e.g., $2b$10$... ).

4. Crucially: It does not decrypt the database password. It hashes


the input using the same salt and compares the two hashes.

Result: Returns true or false .

Step 5: Session Token Generation (JWT)

File: server/config/models/[Link] & server/controllers/[Link]

Action: If the password matches, the controller calls


[Link]() .

Logic: It uses the jsonwebtoken library to create a digital token. It


embeds the user's _id and email into the token payload and signs it
with the server's private JWT_SECRET .

Step 6: Final Response & Storage

Files: server/controllers/[Link] → client/src/pages/[Link]

Backend: Responds with status 200 OK and the JSON object { token,

user } .

Frontend: Receives the token and immediately saves it to localStorage .

Result: The user is now "authenticated." Future requests to the


portfolio will grab this token from storage to prove identity.

2. Why We Used This (Technical Decisions)


Why use POST for Login? We aren't creating a user.

Reason: Security. While GET is for fetching data, it appends


parameters to the URL (e.g., .../login?pass=123 ). URLs are saved in
browser history and server logs, exposing the password. POST
sends data in the request body, which is encrypted over HTTPS.

Why define isValidPassword in the Model instead of the Controller?

Reason: Encapsulation. The logic of "how to check a password"


belongs to the User entity itself. The controller's job is just to
manage the flow (Receive Request -> Ask Model -> Send

FINSIGHT TCS 11
Response). This keeps the controller clean ("Skinny Controller, Fat
Model").

Why use [Link] ? Why not just dbPassword === inputPassword ?

Reason: Since we hashed the password during registration, the


database stores a scramble like $2b$10$XyZ... . The user types
"password123". These strings are totally different. [Link]
handles the complex math of re-hashing the input with the stored
salt to see if they match.

Why localStorage instead of Cookies?

Reason: Simplicity vs. Security trade-off. For a fresher/MERN


project, localStorage is easier to implement because you just
read/write it with JavaScript. Cookies (specifically HttpOnly ) are more
secure against XSS attacks but require more complex CORS and
backend configuration.

3. Important Interview Questions (User Login)


1. Explain the difference between Authentication and Authorization.
Which one is "Login"?

Answer: Authentication is verifying who you are (Login).


Authorization is verifying what you can do (Accessing the Portfolio
page). Login is purely Authentication.

2. In your loginUser function, why do you fetch the user before checking
the password?

Answer: I need the "Salt" and the "Hash" from the database to
verify the password. I can't verify the password if I don't know
which user's hash to compare it against.

3. What happens if the JWT_SECRET key on your server is leaked?

Answer: A hacker could generate their own fake tokens and sign
them with your secret. They could then impersonate any user
(including admins) without knowing their passwords.

4. What is the structure of a JWT?

Answer: It has three parts separated by dots:

1. Header: Algorithm used (HS256).

FINSIGHT TCS 12
2. Payload: Data (User ID, Email, Expiry).

3. Signature: The verification string created using the Secret Key.

5. Why do you return a 404 for "User not found" and 401 for "Wrong
Password"?

Answer: 404 indicates the resource (User account) does not exist.
401indicates "Unauthorized" – the user exists, but they failed to
provide valid credentials. (Note: Ideally in production, use a generic
"Invalid credentials" message for both to prevent user
enumeration).

6. If I refresh the page, how does your app remember I am logged in?

Answer: Because I saved the JWT in localStorage upon successful


login. When the app reloads, it checks localStorage , finds the token,
and includes it in the Authorization header for API requests.

7. What is a "Salt" in the context of bcrypt ?

Answer: A random string added to the password before hashing. It


ensures that even if two users have the same password ("123456"),
their stored hashes are different. bcrypt stores the salt inside the
hash string so it can extract it during comparison.

8. Why is async/await necessary in the login controller?

Answer: Finding a user in MongoDB ( findUserByEmail ) and comparing


hashes with Bcrypt ( isValidPassword ) are operations that take time.
await ensures we don't send a response before these operations are

finished.

9. What is the standard HTTP header used to send the Token?

Answer: The Authorization header. The standard format is Bearer <token> .

10. Can the server "read" the password from the database to send it to
the user if they forgot it?

Answer: No. Because we use hashing (one-way), even the server


cannot reverse the hash to get the original password. The only
solution is to reset/overwrite it with a new one.

Protected Routes

Step 1: The Client-Side Guard (Frontend)

FINSIGHT TCS 13
File: client/src/pages/[Link]

Action: When the user tries to load the Dashboard ( / ), the useEffect

hook runs immediately.

The Check: It looks for a token in localStorage .

Result: If no token is found, it forces a navigation to /login . This is


the first line of defense (User Experience defense).

Step 2: Attaching the "Pass" (Axios Interceptor)

File: client/src/config/[Link]

Action: If the user is logged in, the frontend needs to fetch data
(e.g., "Get my holdings").

Automation: Instead of manually adding the token to every single


API call, we use an Axios Interceptor.

Logic: Before any request leaves the browser, this code runs. It
grabs the token from localStorage and injects it into the HTTP Header:
Authorization: Bearer <token> .

Step 3: The Server-Side Guard (Middleware)

File: server/routes/[Link]

Action: The request hits the backend route /get .

The Gatekeeper: Notice the code: [Link]("/get", verifyLogin,

. The request cannot reach


getUserHoldings) getUserHoldings without
passing through verifyLogin first.

Step 4: Verifying the Token (The Logic)

File: server/middleware/[Link]

Action: The verifyLogin function executes.

Extraction: It checks the Authorization header. It splits the string "Bearer

<token>" to get just the token part.

Verification: It uses [Link](token, [Link].JWT_SECRET) . This checks


the digital signature. If the token was tampered with or expired, it
throws an error.

Identification: It takes the _id from the token and finds the user in
the database.

FINSIGHT TCS 14
Attachment: It adds the user object to the request: [Link] = user .
This is crucial for the next step.

Approval: Finally, it calls next() , which tells Express "Let them in".

Step 5: Accessing Data (Controller)

File: server/controllers/[Link]

Action: The getUserHoldings function finally runs.

Usage: Because of Step 4, it can now simply use [Link]._id to query


the database and find only the holdings that belong to this specific
user.

2. Why We Used This (Technical Decisions)


Why use "Middleware" instead of checking the token in every
controller function?

Reason: DRY (Don't Repeat Yourself). If we verified the token


inside every controller, we would duplicate code 50 times.
Middleware allows us to write the logic once and apply it to any
route that needs protection.

Why the "Bearer" prefix in the header?

Reason: Standard Convention. It is a standard HTTP authentication


scheme (RFC 6750). It tells the server "I am bearing (holding) a
token that gives me access." While you could omit it, following
standards ensures compatibility with other security libraries and
tools.

Why attach [Link] in the middleware?

Reason: Context Passing. The middleware figures out who the user
is. The controller needs to know whose data to fetch. By attaching
the user to the request object ( req ), we pass that information
seamlessly to the next function in the chain.

Why check on BOTH Client and Server?

Reason: UX vs. Security.

Client Check: Fast. Improves User Experience by redirecting


them immediately without waiting for a server error. But it is

FINSIGHT TCS 15
insecure (users can disable JS).

Server Check: Slow (network call), but Secure. This is the real
enforcement. Even if someone uses Postman to bypass the
frontend, the server will block them.

3. Important Interview Questions (Protected Routes)


1. What is "Middleware" in [Link]?

Answer: Middleware is a function that has access to the Request


( req ) and Response ( res ) objects. It sits "in the middle" between the
raw request and the final controller. It can modify the request,
execute code (like auth checks), or end the request early.

2. Explain the role of the next() function.

Answer: next() is a callback that passes control to the next


middleware function in the stack. If I don't call next() (and don't send
a response), the request will hang indefinitely until it times out.

3. If I manually edit the JWT string in my localStorage , what happens?

Answer: The server's [Link]() function will fail because the


signature will no longer match the payload. The middleware will
throw an error, and the server will return a 401 Unauthorized status.

4. How does your application handle Token Expiration?

Answer: (Based on your code) The current [Link] will throw a


TokenExpiredError if the token is old. My middleware catches this error

and sends a 401 response. The frontend would then likely need to
redirect the user to login again.

5. What is an "Interceptor" in Axios and why is it useful?

Answer: An interceptor allows us to catch every outgoing request


and modify it before it is sent. I used it to automatically inject the
Authorization header into every API call, so I don't have to manually

add it in every component.

6. Why do we need [Link].JWT_SECRET in the middleware?

Answer: This secret key is used to verify the digital signature of the
JWT. Without it, we cannot confirm if the token was issued by us or
if it was forged by a hacker.

FINSIGHT TCS 16
7. What is the difference between 401 Unauthorized and 403 Forbidden ?

Answer: 401 means "I don't know who you are" (Authentication
failed/Missing token). 403 means "I know who you are, but you
aren't allowed to do this" (Authorization failed, e.g., a regular user
trying to delete an admin). Ideally, your middleware sends 401 for
invalid tokens.

8. Can we protect a route on the Frontend without checking the


Backend?

Answer: Visually, yes (we can hide the page). Securely, no. An
attacker can always send direct HTTP requests to the backend API
using tools like Postman, bypassing the frontend entirely.

9. Why do you query the database ( [Link] ) inside the middleware?


Isn't the ID in the token enough?

Answer: While the token has the ID, querying the database ensures
the user still exists (hasn't been deleted) and allows us to attach the
latest user data (like roles or permissions) to [Link] for the
controller to use.

10. In your [Link] , you have [Link]("/get", verifyLogin, getUserHoldings) .


What is the order of execution?

Answer: It executes left to right. First verifyLogin runs. If it succeeds


(calls next ), then getUserHoldings runs. If verifyLogin fails (sends
response), getUserHoldings is never called.

Portfolio Management

Add Transaction

Step 1: User Input (Frontend Modal)

File: client/src/pages/[Link]

Action: The user clicks "Add transaction", opening a modal. They


enter the Ticker (e.g., AAPL), Quantity (10), Price (150), and Date.

Smart Search: As they type the ticker, fetchStockSuggestions calls a


Polygon API to suggest valid stock symbols.

FINSIGHT TCS 17
Submission: When the form is submitted, [Link] sends the data to
${API}/holdings/add .

Step 2: Route Protection (Backend)

File: server/routes/[Link]

Action: The request hits [Link]("/add", verifyLogin, addHolding) .

Guard: The verifyLogin middleware ensures only logged-in users can


add transactions. It attaches the user's ID to [Link] .

Step 3: Controller Logic - The Decision Maker

File: server/controllers/[Link]

Action: The addHolding function starts.

Validation: Checks if ticker, quantity, and price are provided.

Existing Check: It fetches the user's current holdings to see: Does


this user already own this stock?

External Data Fetch: Before saving, it calls


[Link](ticker) to get the company name, sector,

and market cap automatically. This enriches the data without the
user typing it.

Step 4: Branch A - Updating an Existing Holding

File: server/controllers/[Link]

Scenario: The user already owns "AAPL".

The Math (Weighted Average): The system calculates a new


average price.

Formula: (OldQty * OldPrice + NewQty * NewPrice) / TotalQty

Update: It updates the existing MongoDB document with the new


Total Quantity and Average Price . It does not create a new row.

Step 5: Branch B - Creating a New Holding

File: server/controllers/[Link]

Scenario: The user does not own "AAPL".

History Fetch: It calls fetchPriceHistory to get the last 100 days of price
data for the chart and saves it to the StockPrice collection.

FINSIGHT TCS 18
Creation: It calls createHolding (Service Layer) to insert a brand new
document into the Holdings collection with the user's input and the
fetched metadata.

Step 6: Database Storage

File: server/config/models/[Link]

Action: The data is stored.

Structure: The document contains the userId , ticker , financial data


( purchasePrice , quantity ), and the stockMetadata (Sector, Market Cap).

2. Why We Used This (Technical Decisions)


Why calculate Weighted Average Price instead of keeping separate
"lots"?

Reason: Simplicity. For a personal dashboard, seeing one unified


view of "AAPL" with an average price is cleaner than seeing 5
different rows for "AAPL" bought at different dates.

Why fetch Metadata (Sector, Market Cap) during the Add process?

Reason: Data Enrichment. The user only knows the Ticker. By


fetching metadata immediately, we populate the "Insights" charts
(Sector Allocation) instantly without needing a background job to fill
in the blanks later.

Why separate StockPrice (History) from Holding (User Ownership)


models?

Reason: Normalization & Efficiency. Historical price data is heavy


(hundreds of array entries). If 1,000 users own Apple, we don't want
to save Apple's 100-day history 1,000 times. We save it once in
StockPrice and link it by ticker.

Why use [Link] instead of fetch ?

Reason: Boilerplate reduction. Axios automatically handles JSON


stringifying, headers (via our interceptor), and error status codes,
making the frontend code concise.

3. Important Interview Questions (Add Transaction)

FINSIGHT TCS 19
1. Explain the logic you used to handle buying more of a stock you
already own.

Answer: I implemented a "Weighted Average Price" calculation.


Instead of creating a duplicate record, I find the existing holding,
multiply the old price by old quantity, add the new investment, and
divide by the total new quantity.

2. How do you handle the situation where the External API (Yahoo
Finance) fails while adding a stock?

Answer: I have a try/catch block. If the metadata fetch fails, I log the
error but continue the process using [Link] .
This ensures the user can still add the transaction even if the extra
data is temporarily unavailable.

3. Why do you verify the token ( verifyLogin ) on this route?

Answer: To ensure that the holding is assigned to the correct user. I


use the [Link]._id (extracted from the token) to link the holding to
the user account. Without this, anyone could corrupt another user's
portfolio.

4. What is the difference between PUT and POST ? Which one did you use
here?

Answer: I technically used POST for the endpoint /add . However,


logically, my controller performs an "Upsert" (Update if exists, Insert
if new). In a strict REST API, this might be split, but for simplicity, a
single endpoint handles the logic.

5. How does your backend validate that the "Quantity" is not negative?

Answer: I handled this at the Database level using Mongoose


Schema validation. The quantity field has min: 1 set, so MongoDB will
reject any save attempt with invalid numbers.

6. Explain the relationship between the Holding model and the User

model.

Answer: It is a "One-to-Many" relationship. One User can have


Many Holdings. In the Holding schema, I store a userId reference ( type:
[Link] ) to link them.

7. What is an "Upsert" operation?

FINSIGHT TCS 20
Answer: It stands for "Update or Insert". It's a database operation
where if a record matches criteria, it updates it; otherwise, it creates
a new one. My controller implements this logic manually (Find -> If
Found Update -> Else Create).

8. Why do you save stockMetadata inside the Holding document? Doesn't


that duplicate data?

Answer: Yes, it is a form of denormalization. I did this for read


performance. When loading the portfolio, I want the Sector/Industry
available immediately without performing a costly "Join" ($lookup)
with another collection or calling an external API every single time.

9. If two users add "AAPL" at the exact same millisecond, what


happens?

Answer: Since each holding is unique to a userId + ticker , there is no


conflict between users. If the same user sends two requests,
MongoDB's atomic locking usually handles it, though race
conditions in the "read-then-write" logic in [Link] are theoretically
possible without transactions.

10. How do you ensure the ticker is saved consistently (e.g., AAPL vs
aapl)?

Answer: In the addHolding controller (or schema), I force the ticker to


uppercase ( [Link]() ) before saving or querying. This
ensures case-insensitive matching.

Smart Holding Updates

Step 1: The Trigger (Frontend)

File: client/src/pages/[Link]

Scenario: User already owns 10 AAPL shares at $100.

Action: User clicks "Add Transaction" and enters: Ticker: AAPL,


Quantity: 10, Price: $200.

Request: [Link] sends this data to the backend.

Step 2: Detection (Backend Controller)

File: server/controllers/[Link]

Action: The addHolding function starts.

FINSIGHT TCS 21
Query: It calls getHoldings(userId) to fetch the user's current portfolio.

Search: It runs [Link](h => [Link] === ticker) .

Result: It finds the existing document for AAPL. The code now
enters the "Update" branch instead of the "Create" branch.

Step 3: Optimization Strategy (Metadata)

File: server/controllers/[Link]

Logic: The system decides how to get stock details (Sector, Market
Cap).

Smart Cache: Since the holding already exists, the code assumes
we likely have recent data. It calls
[Link](ticker) .

Why? This saves an expensive external API call to Yahoo


Finance/RapidAPI, making the update significantly faster.

Step 4: The Algorithm (Weighted Average Calculation)

File: server/controllers/[Link]

Input:

PrevQty = 10, PrevPrice = 100 (Invested: $1,000)

AddQty = 10, AddPrice = 200 (Invested: $2,000)

Calculation:

1. TotalQuantity = 10 + 10 = 20

2. TotalInvested = (10 * 100) + (10 * 200) = 3000

3. AvgPrice = 3000 / 20 = 150

Result: The new "Buy Price" is mathematically updated to $150.

Step 5: Atomic Update (Database)

File: server/controllers/[Link]

Action: Instead of save() , it calls [Link](...) .

Command: It uses the MongoDB $set operator to replace specific


fields:

quantity : 20

FINSIGHT TCS 22
purchasePrice : 150

stockMetadata : (Refreshed from cache)

Efficiency: This modifies the existing document in place rather than


deleting and re-inserting it.

Step 6: Response

Backend: Returns 200 OK with the message "Holding updated


successfully" and the new average price.

Frontend: The Portfolio page refreshes, and the user sees their
AAPL position has grown, with the P&L recalculated based on the
new average of $150.

2. Why We Used This (Technical Decisions)


Why calculate Weighted Average Cost (WAC)?

Reason: Standard Accounting Practice. In portfolio management,


"Averaging Down" (buying more when price drops) or "Averaging
Up" is a key strategy. Keeping separate rows for every buy makes it
hard to see the true breakeven point of the investment.

Why use updateOne with $set ?

Reason: Performance & Atomicity. save() overwrites the entire


document. $set only touches the fields that changed. It is generally
faster and reduces network overhead between the Node server and
MongoDB.

Why prefer Cached Metadata for updates?

Reason: Rate Limiting. External APIs (Yahoo Finance) usually have


strict limits (e.g., 500 calls/day on free tiers). Since the user already
has the stock, the metadata (Sector, Industry) hasn't changed. We
save our API quota for new stocks where we really need that data.

Why cast inputs to Number() ?

Reason: Type Safety. Data from HTML forms or JSON payloads


can sometimes arrive as strings ( "10" ). JavaScript concatenation
( "10" + "10" = "1010" ) would be disastrous for financial apps. Explicit
casting ensures math operations work correctly ( 10 + 10 = 20 ).

FINSIGHT TCS 23
3. Important Interview Questions (Smart Updates)
1. Explain the algorithm you used to average the stock price.

Answer: I used the Weighted Average formula: ((Old_Qty * Old_Price) +


(New_Qty * New_Price)) / (Old_Qty + New_Qty) . This ensures the new price

accurately reflects the total capital invested divided by total shares.

2. What is a "Race Condition"? If two requests to add "AAPL" arrive at


the exact same time, what happens to the quantity?

Answer: A race condition occurs if two processes read the "Old


Qty" (10) simultaneously, add 5, and both write back "15" (instead of
20).

Follow-up: To fix this in production, I would use MongoDB's $inc

operator (atomic increment) instead of calculating it in [Link], or


use Database Transactions.

3. Why did you choose to update the existing document instead of


creating a "Transaction History" table?

Answer: For a personal dashboard, a summarized view (current


snapshot) is faster to query and easier to visualize. A full
Transaction History table would require complex aggregation
queries ( $group , $sum ) every time the user loads the page, which
impacts performance.

4. In JavaScript, 0.1 + 0.2 !== 0.3 . How did you handle floating-point
precision errors in your price calculation?

Answer: JavaScript uses IEEE 754 floating-point math. While my


current code does raw division, in a production financial app, I
would use a library like [Link] or store values in cents (integers) to
avoid precision loss.

5. What is the difference between findByIdAndUpdate and the approach you


used (Find then Update)?

Answer: findByIdAndUpdate is one atomic database call. My approach


(Find -> Calculate in JS -> Update) takes two calls. I used the two-
step approach because the calculation (Weighted Average) requires
application-level logic that is hard to express in a single MongoDB
update query.

FINSIGHT TCS 24
6. Why do you use Number() to convert the inputs? Doesn't Mongoose
handle types?

Answer: Mongoose does cast types upon saving, but my logic


performs math before the save (calculating avgPrice ). If I essentially
did "100" * "10" , JS works, but "10" + "10" becomes "1010" . Explicit
casting prevents logic errors before they reach the database layer.

7. What happens to the "Notes" field when updating? Does it overwrite


the old notes?

Answer: Yes, in my current logic notes are part of the $set object, so
the new transaction's notes overwrite the previous ones. Ideally, I
could append the new note to an array of notes.

8. How does your system minimize external API usage during updates?

Answer: I implemented a check: if the holding exists, I call


getCachedStockMetadata instead of fetching fresh data from Yahoo

Finance. This effectively cuts API calls in half for recurring


investors.

9. If the user sells stock, can they use this same "Add Transaction"
endpoint?

Answer: The current logic is designed for adding (buying). A sell


transaction would require different math (reducing quantity,
calculating Realized P&L), which would likely need a separate
endpoint or a "Type" flag (Buy/Sell) in the request.

10. Why is upsert: true not used in your addHolding function?

Answer: upsert (Update or Insert) works great for simple


replacements. However, my "Insert" logic (Fetch History) is
completely different from my "Update" logic (Calc Average).
Splitting them into an if/else block gives me granular control over
what happens in each scenario.

portfolio

Step 1: Initialization (Frontend)

File: client/src/pages/[Link]

Action: The component mounts. The useEffect hook triggers the


primary data fetching function: fetchStocksAndPrices() .

FINSIGHT TCS 25
State: The dashboard enters a loading state, showing spinners
instead of tables.

Step 2: Fetching Core Data (Parallel Requests)

File: client/src/pages/[Link]

Action: The frontend fires off multiple asynchronous requests to the


backend:

1. Holdings: GET /holdings/get to retrieve the user's static stock data


(Ticker, Qty, Buy Price).

2. Insights: GET /holdings/insights to get the sector allocation and risk


metrics.

3. History: GET /portfolio/net-worth-history to get data for the line chart.

Step 3: Fetching Live Market Data (Backend Proxy)

File: client/src/pages/[Link] & server/controllers/[Link]

Action: Once the Holdings are received, the frontend extracts a list
of all tickers (e.g., ["AAPL", "TSLA"] ).

Request: It sends this list to GET /stock/last-price?tickers=AAPL,TSLA .

Backend Logic: The server looks up the latest cached price for
these specific tickers from the StockPrice collection (or fetches fresh
if needed) and returns them in a key-value map: { "AAPL": 150.00, "TSLA":

200.00 } .

Step 4: Data Merging & Calculation (Client-Side Logic)

File: client/src/pages/[Link]

Action: The frontend now has two arrays: Holdings (Static) and Prices

(Live).

The Merge: It loops through holdings and matches them with the
live price.

The Math: It calculates the "live" metrics in the browser:

Current Value = Quantity * Live Price

Profit/Loss = Current Value - (Quantity * Buy Price)

Total Portfolio Value = Sum of all individual values .

FINSIGHT TCS 26
Why here? Doing this math on the client reduces server CPU load
and allows for instant UI updates if we add real-time sockets later.

Step 5: Visualization (Rendering)

File: client/src/pages/[Link]

Action: React renders the processed data.

Line Chart: Uses react-chartjs-2 to draw the chartData (Net Worth


over time).

Speedometer: Uses react-d3-speedometer to show the overall %

Return (Green for profit, Red for loss).

Table: Renders the list of stocks with their calculated P&L and a
colored progress bar indicating their "Weight" in the portfolio.

2. Why We Used This (Technical Decisions)


Why calculate Total Value on the Frontend instead of the Backend?

Reason: Distributed Computing. The server's job is to provide data


(Holdings, Prices). The client's job is to provide presentation (Totals,
Formatting). If the server calculated the total, it would have to re-run
that logic every time a price changed. By moving it to the client (the
user's browser), we distribute that computational load across
thousands of users instead of one server.

Why fetch "Holdings" and "Last Price" in separate API calls?

Reason: Decoupling & Caching.

Holdings change rarely (only when user buys/sells).

Prices change constantly (every second).

Separating them allows us to potentially cache the "Holdings"


request aggressively while keeping the "Prices" request
frequent and fresh.

Why use [Link] ?

Reason: Industry Standard. It is one of the most mature and widely


documented charting libraries for JavaScript. It handles responsive
resizing, tooltips (hovering over points), and animations out of the
box, which would be very hard to build from scratch.

FINSIGHT TCS 27
Why use a "Speedometer" for Portfolio Health?

Reason: Cognitive Ease. A number ("+15%") is abstract. A gauge


pointing to the "Green Zone" is instantly understood as "Good." It
gamifies the investment experience.

3. Important Interview Questions (Portfolio Dashboard)


1. Why did you choose to perform the P&L (Profit & Loss) calculations on
the frontend?

Answer: To offload computational work from the server. The server


provides the raw ingredients (Holdings + Prices), and the client
"cooks" them (Calculates Totals). This makes the backend more
scalable.

2. How do you handle the "Loading State" when fetching data from 3
different endpoints?

Answer: I used a state variable loading . I initiate the requests, and I


only set loading = false once the critical data (Holdings + Prices) has
resolved. I display a skeleton loader or spinner while loading is true
to improve User Experience.

3. Explain how useEffect is used in your Dashboard component.

Answer: I use useEffect with an empty dependency array [] to trigger


the initial data fetch when the component mounts. I use a second
useEffect dependent on [selectedRange] to re-fetch the chart data

whenever the user switches between "1D", "1W", or "1M" views.

4. How would you optimize this dashboard if the user had 10,000
holdings?

Answer: Rendering 10,000 rows would freeze the browser. I would


implement Virtualization (using a library like react-window ) to only
render the rows currently visible on the screen. I would also
implement server-side pagination for the API.

5. What is the "Prop Drilling" problem and did you face it here?

Answer: Prop drilling is passing data through many layers of


components. Since my Dashboard is a single large page ("Smart
Component"), I didn't face it much here. However, if I broke the

FINSIGHT TCS 28
"Summary Cards" into separate components, I would pass the
portfolioSummary object as a prop.

6. Why do you verify the token on the /last-price endpoint? Isn't stock
price public info?

Answer: While the price is public, accessing my API consumes


server resources and API quota (from Yahoo Finance/RapidAPI).
Protecting the route ensures only registered users can consume my
API credits.

7. How does the frontend know which currency symbol (₹ vs $) to


display?

Answer: Currently, it is hardcoded to ₹ or uses toLocaleString('en-IN') for


Indian numbering formatting. Ideally, this would be a user
preference stored in the database settings.

8. If the stock/last-price API fails, does the whole dashboard crash?

Answer: No. The fetchStocksAndPrices function has try/catch blocks. If


fetching prices fails, the holdings will still load, but the "Current
Value" and "LTP" columns might show "N/A" or fallback to the
purchase price, ensuring the app remains usable.

9. What is the difference between Client-Side Rendering (CSR) and Server-Side

Rendering (SSR)? Which one is this?

Answer: This is Client-Side Rendering (CSR). The server sends an


empty HTML shell, and React (JavaScript) fetches the data and
populates the DOM in the browser.

10. Explain the reduce function logic if you were calculating the "Total
Portfolio Value" on the server.

Answer: I would use [Link]((sum, holding) => sum + ([Link] *

. This iterates through the array, accumulating the


[Link]), 0)

value of each holding into a single total sum.

Net Worth History


Here is the deep-dive breakdown for Feature 7: Net Worth History.
This feature visualizes the user's financial journey over time using an
interactive line chart. It involves complex data aggregation and date
manipulation on the backend.

FINSIGHT TCS 29
Feature 7: Net Worth History

1. The Proper Flow (Start to End)


Step 1: User Interaction (Frontend)

File: client/src/pages/[Link]

Action: When the Dashboard loads, or when the user clicks a range
button (1D, 1W, 1M, 1Y), the useEffect hook triggers.

Request: It calls which sends a GET request to


fetchNetWorthHistory()

${API}/portfolio/net-worth-history?range=month (or the selected range).

Step 2: Route Handling (Backend)

File: server/routes/[Link]

Action: The router receives the request and directs it to the


getNetWorthHistory function in the [Link] .

Security: This route is protected by verifyLogin , so [Link]._id is


available.

Step 3: Calculating Time Ranges (Controller)

File: server/controllers/[Link]

Logic: The controller first defines the "Start Date" and the
"Grouping Strategy" based on the requested range.

Day: Start = Now - 30 days. Group by Day (YYYY-MM-DD).

Month: Start = Now - 12 months. Group by Month (YYYY-MM).

Year: Start = Now - 5 years. Group by Year (YYYY).

Why? This limits the amount of data processed. We don't need 5


years of daily data if the user only asked for "1 Month".

Step 4: Fetching Raw Data

File: server/controllers/[Link]

Action 1 (Holdings): It fetches the user's current holdings to know


which stocks they own and how many ( quantities map).

Action 2 (Prices): It fetches the entire price history documents from


the StockPrice collection for those specific tickers.

FINSIGHT TCS 30
Step 5: The Aggregation Algorithm (Backend Math)

File: server/controllers/[Link]

The Logic:

1. Grouping: It iterates through every historical price point for


every stock. If the date falls within the range, it assigns it to a
"Group Key" (e.g., "2023-10-01").

2. Summation: It creates a grouped object where keys are dates.

3. Calculation: It loops through these dates. For each date, it


calculates: Sum(Price of Ticker on that Date * Current Quantity) .

Result: It transforms this map into a sorted array: [{ date: '2023-10-01',

value: 50000 }, { date: '2023-10-02', value: 51000 }] .

Step 6: Visualization (Frontend)

File: client/src/pages/[Link]

Action: The frontend receives the array.

Styling: It checks if the latest value is higher than the first value. If
Latest > First , it sets the chart line color to Green; otherwise, it sets it

to Red.

Rendering: It passes this data to the Line component from react-

chartjs-2 to render the graph.

2. Why We Used This (Technical Decisions)


Why calculate history based on Current Quantity?

Reason: Approximation vs. Complexity. Technically, to get true


historical net worth, we would need a "Transaction Ledger"
(snapshot of exactly how many shares you owned on every single
past date). This is extremely complex to build.

Decision: We assume "If I held my current portfolio 1 year ago, what


would it have been worth?" This is a standard simplification for
personal dashboards known as "Backtesting view."

Why group dates in JavaScript instead of MongoDB Aggregation?

Reason: Data Structure Limitations. The StockPrice model stores


prices in an array ( values: [...] ). Unwinding massive arrays and

FINSIGHT TCS 31
grouping them across multiple documents (tickers) in MongoDB can
be slow and complex to write. Doing the loop in [Link] (V8 engine)
is often faster for datasets of this size (thousands of points, not
millions).

Why dynamic grouping (Day vs Week vs Month)?

Reason: Performance & Readability.

1 Year View: Showing 365 points makes the chart crowded and
slow to render. Grouping by "Month" reduces this to 12 points.

1 Month View: Showing 30 points (Daily) is perfect for detail.

Why StockPrice collection separate from Holdings ?

Reason: Normalization. Multiple users might own "AAPL". If we


stored price history inside every user's holding document, we would
duplicate the same data thousands of times. A separate StockPrice
collection acts as a "Single Source of Truth" for market data.

3. Important Interview Questions (Net Worth History)


1. Explain the "Backtesting" assumption in your history calculation.
What is the limitation?

Answer: My code calculates historical value based on current


holdings ( currentQty * historicalPrice ). The limitation is that it doesn't
account for when the user actually bought the stock. If they bought
it yesterday, the chart will still show a value for it 1 year ago,
effectively showing "What this portfolio would have been worth."

2. How did you handle "Missing Data" (e.g., weekends or holidays


where stock markets are closed)?

Answer: My logic iterates through the stored price history. If no


price exists for a specific date (like a Sunday), that date simply
doesn't appear in the grouped map, or I could implement a "fill
forward" strategy (use Friday's price for Saturday/Sunday) to make
the line smooth.

3. Why do you fetch all price history ( [Link] ) instead of filtering by


date in the database query?

FINSIGHT TCS 32
Answer: (Critique of current code): Currently, I fetch the document
which contains the entire array of history, then filter in JS. Ideally, I
should use MongoDB projection ( $slice or $filter ) to retrieve only the
relevant date range from the database to save bandwidth.

4. How does the groupKey function work in your controller?

Answer: It's a dynamic function. If the user selects "Year", the


function returns [Link]() (e.g., "2023"). This allows me to
bucket all data points from 2023 into a single key in my object,
effectively averaging or taking the latest price for that year.

5. This operation seems CPU intensive (Nested Loops). How would you
scale this?

Answer: I would pre-calculate and cache the "Portfolio Value


Snapshot" every night.

Solution: Create a PortfolioHistory collection. Every night (via Cron


Job), calculate the total value once and save it { userId, date, totalValue } .
The API would then just read this simple table instead of calculating
math on the fly.

6. How did you determine the color of the chart line (Green/Red)?

Answer: On the frontend, I compare the first data point (start of


range) with the last data point (today). isUptrend = last > first . If true,
color is #00FF00 , else #FF2D2D .

7. What happens if a user has stocks in different currencies (USD vs


INR)?

Answer: Currently, the system seems to assume a unified currency


or ignores conversion (calculating raw sum). In a real-world app, I
would need a currency conversion layer (e.g., price * exchangeRate )
before summing them up.

8. Why use Date objects in JavaScript? Are there issues with


Timezones?

Answer: Yes, Date in JS uses the server's local time (or UTC). This
can cause "off-by-one-day" errors if the server is in UTC but the
user is in IST. Using a library like [Link] or date-fns and storing
everything in UTC is the standard solution.

FINSIGHT TCS 33
9. Explain the data structure sent to the frontend for [Link] .

Answer: [Link] expects two arrays: labels (the X-axis, which are
my dates) and datasets (containing the data array of Y-axis values).
My controller prepares a clean JSON array, and the frontend maps
it: labels: [Link](d => [Link]) , data: [Link](d => [Link]) .

10. How does the StockPrice model store data?

Answer: It uses an array of sub-documents: { ticker: "AAPL", values: [{ date:


..., close: ... }, ...] } . This is the "Bucket Pattern" in MongoDB, which is

efficient for time-series data.

Portfolio Insight

Step 1: Triggering the Analysis (Frontend)

File: client/src/pages/[Link]

Action: When the dashboard loads ( fetchStocksAndPrices ), the frontend


requests the analytical data separately from the raw list of stocks.

Request: [Link]( ${API}/holdings/insights ) .

Step 2: Authenticated Access (Backend)

File: server/routes/[Link]

Action: The request is intercepted by verifyLogin .

Purpose: Ensures we only calculate insights for the logged-in


user's portfolio.

Step 3: Aggregation Logic (Backend Controller)

File: server/controllers/[Link]

Action: The getPortfolioInsights function executes.

Retrieval: It fetches all holdings for the user from MongoDB.

The Loop: It iterates through every single holding to build the


aggregate data:

1. Valuation: It calculates the value of the position. Note: In the


current backend logic, it uses purchasePrice (Invested Value)
because live prices aren't stored in the Holding document.

2. Sector Bucketing: It checks [Link] (e.g.,


"Technology") and adds the value to that sector's total.

FINSIGHT TCS 34
3. Market Cap Bucketing: It checks
[Link] (e.g., "Large Cap") and adds to
that bucket.

4. Risk Metrics: It accumulates weighted P/E ratios and ROE


(Return on Equity) to calculate a portfolio-wide average.

Step 4: Scoring & Calculations (The "Insights")

File: server/controllers/[Link]

Diversification Score: It counts the number of unique sectors. The


score is calculated as min(numberOfSectors / 5, 1) * 100 . (5+ sectors = 100%
score).

Weighted Averages: It divides the accumulated P/E sum by the total


portfolio value to give a "Portfolio P/E".

Top Performers: It calculates the % return for each stock and sorts
them to find the winners.

Step 5: Formatting & Response

File: server/controllers/[Link]

Conversion: It converts the absolute values (e.g., "Tech: $5000")


into percentages (e.g., "Tech: 50%") for easier frontend rendering.

Response: Sends a JSON object containing { sectorAllocation,

marketCapAllocation, riskMetrics, topPerformers } .

Step 6: Visualization (Frontend Rendering)

File: client/src/pages/[Link]

Action: The Doughnut chart component receives this data.

View Switching: A dropdown allows the user to switch the chart


view:

"Holdings": Uses frontend live data.

"Sectors": Uses the backend sectorAllocation data.

"Market Cap": Uses the backend marketCapAllocation data.

2. Why We Used This (Technical Decisions)


Why calculate Sector/Market Cap on the Backend?

FINSIGHT TCS 35
Reason: Data integrity and caching. The stockMetadata (Sector,
Industry) lives in the database. If we sent raw metadata to the
frontend for every stock, the payload would be huge. Aggregating it
on the server sends a tiny summary (e.g., {"Tech": 50, "Finance": 50} )
instead of massive objects.

Why use stockMetadata stored in the Holding document?

Reason: Performance. Doing a "Join" or calling Yahoo Finance API


for every stock every time you load the dashboard to find out "AAPL
is Tech" would be incredibly slow. Storing this static data alongside
the holding makes the Insights endpoint instant.

Why Weighted Averages for P/E?

Reason: Financial Accuracy. A simple average is misleading. If you


have $10 in a high P/E stock and $1,000,000 in a low P/E stock, your
portfolio is effectively "Low P/E." Weighting by value reflects the
true risk exposure.

Why use a Doughnut Chart?

Reason: Part-to-Whole Relationship. Doughnut/Pie charts are the


standard UI pattern for visualizing allocation (how 100% of your
money is divided).

3. Important Interview Questions (Portfolio Insights)


1. In your controller, you calculate currentValue using [Link] ||

. Since
[Link] livePrice isn't in your Schema, what value is
actually being used?

Answer: Good catch. Since livePrice is not stored in the database, it


will be undefined , so the backend defaults to using purchasePrice . This
means the Sector Allocation chart currently represents "Allocation
by Invested Capital," not "Current Market Value." To fix this, I would
need to pass live prices to the insights endpoint or fetch them within
the controller.

2. How do you calculate the "Diversification Score"?

Answer: It is a simple heuristic based on the count of unique


sectors. I count the keys in the sectorAllocation object. If the user has 5

FINSIGHT TCS 36
or more sectors, they get 100%. If they have 1, they get 20%. It
encourages users to not put all eggs in one basket.

3. Explain the logic for "Weighted Average P/E Ratio".

Answer: I iterate through the holdings. For each stock, I multiply its
by its Total Value . I sum these products up ( peSum ). Finally, I
P/E Ratio

divide this sum by the Total Portfolio Value . This ensures larger positions
influence the average more than smaller ones.

4. What happens if a stock's metadata is missing (e.g., "Unknown"


sector)?

Answer: The code handles this gracefully. It checks [Link] ||


"Unknown" . These holdings are grouped into an "Unknown" bucket in

the chart so the app doesn't crash and the total still adds up to
100%.

5. How does the Frontend toggle between "Sector" and "Holdings"


views?

Answer: I use a React state variable insightsView ('allocation', 'sector',


'marketCap'). The getAllocationData function checks this state and
returns different data objects (Labels/Datasets) to the [Link]
component based on the selection.

6. Why do you return percentages and raw values in the API response?

Answer: The raw value is useful for tooltips (e.g., "You have $5,000
in Tech"), while the percentage is useful for the visual proportion of
the chart sections. Sending both prevents the frontend from
needing to re-calculate basic math.

7. How would you optimize this if the user had thousands of holdings?

Answer: I would use MongoDB's Aggregation Framework ( $group ,


$project , $sum ) instead of JavaScript loops ( forEach ). The database

engine is much faster at summing and grouping data than [Link] is


at iterating over array objects.

8. What is the "Market Cap Category" and where does it come from?

Answer: It classifies stocks as "Large", "Mid", or "Small" cap. This


categorization is done logic in my YahooFinanceService when the stock

FINSIGHT TCS 37
is first added, based on its total market capitalization value (e.g., >
$10B is Large Cap).

9. Why use [Link](grouped) to map data?

Answer: The aggregation step creates a JavaScript object (Map)


like . [Link] converts this into an array
{ "Tech": 500, "Health": 200 }

[["Tech", 500], ["Health", 200]] , which makes it easy to map into the format

[Link] expects ( labels array and data array).

10. Does this endpoint return "Top Performers"? How is that calculated?

Answer: Yes. It calculates (CurrentValue - Invested) / Invested * 100 for every


stock, pushes them into an array, sorts the array by percentage
descending, and returns it. This helps the frontend display the
"Winner/Loser" cards without extra logic.

Perfomance Metrices

1. The Proper Flow (Start to End)


This logic happens almost entirely on the Frontend to ensure it uses the
absolute latest real-time prices fetched by the user's browser.

Step 1: Data Gathering (Frontend)

File: client/src/pages/[Link]

Prerequisite: The dashboard has already fetched holdings (from


Database) and livePrices (from API). These are merged into the stock

state array.

Trigger: The component renders. Inside the JSX, there is a logic


block (IIFE - Immediately Invoked Function Expression) that
processes this data on the fly.

Step 2: Normalization (The Map Loop)

File: client/src/pages/[Link]

Action: The code maps over the stock array to create a


standardized stockPerformance array.

Calculation: For every stock, it computes:

Invested = purchasePrice * quantity

Current = livePrice * quantity

FINSIGHT TCS 38
Percentage Return = ((Current - Invested) / Invested) * 100

Filtering: It removes any stocks that don't have a valid livePrice to


prevent NaN (Not a Number) errors or infinite percentages.

Step 3: Finding Extremes (The Reduce Logic)

File: client/src/pages/[Link]

Best Performer: It runs .reduce() on the array. It compares the


percentage of the current stock vs. the "best so far".

Logic: [Link] > [Link] ? current : best

Worst Performer: It runs a second .reduce() to find the lowest


percentage.

Logic: [Link] < [Link] ? current : worst .

Step 4: Conditional Rendering (UI)

File: client/src/pages/[Link]

Action: The React component checks if [Link] exists.

Visuals:

🏆).
Winner: Rendered in a Green Card with a trophy icon (
Shows the Gain % and Profit ₹.

Loser: Rendered in a Red Card with a warning icon (📉). Shows


Loss % and Loss ₹.

Edge Case: If the user only has 1 stock, the "Worst Performer" card
is hidden ( [Link] > 1 ) because the best and worst would
be the same stock.

2. Why We Used This (Technical Decisions)


Why calculate this on the Frontend?

Reason: Freshness. The backend sends purchasePrice . The "Live


Price" is fetched separately in the browser. If we asked the backend
for "Best Performer", it would have to fetch live prices again
(doubling API usage) or use stale cached prices. Calculating it in the
browser guarantees it matches the live prices the user currently
sees.

FINSIGHT TCS 39
Why use .reduce() instead of .sort() ?

Reason: Performance (Big O).

.sort() takes O(N log N) time because it orders the entire array.

.reduce()takes O(N) time because it just scans the list once to


find the max/min.

For 5 stocks, it doesn't matter. For 5,000, reduce is significantly


faster.

Why hide the "Worst Performer" if there is only 1 stock?

Reason: User Psychology/UX. If you buy your first stock and it


goes up, seeing it listed as both "Best Performer" and "Needs
Attention" is confusing and sends mixed signals.

Why filter [Link] before calculating?

Reason: Robustness. If the external API fails for one specific ticker
(returning null ), the math (null - 100) / 100 would break the UI or show
nonsense data. Filtering ensures only valid data enters the
comparison logic.

3. Important Interview Questions (Performance Metrics)


1. Explain how [Link] works. How did you use it to find the
max value?

Answer: reduce iterates through an array and accumulates a single


result. I used it by initializing the accumulator with the first stock,
then comparing each subsequent stock's percentage. If the current
stock is higher, it becomes the new accumulator.

Code: [Link]((max, curr) => [Link] > [Link] ? curr : max, arr[0]) .

2. What is the Time Complexity of your "Best Performer" calculation?

Answer: It is O(N), where N is the number of holdings. I iterate


through the list once (technically twice, once for Best and once for
Worst, which is 2N , but constants are dropped in Big O notation).

3. Why didn't you calculate this in the Backend getPortfolioInsights

endpoint?

FINSIGHT TCS 40
Answer: The backend database ( Holding model) doesn't strictly store
the live price (it stores purchasePrice and cached metadata). The
frontend has the most up-to-date live prices from the separate /last-

price API call, so the calculation is more accurate there.

4. What happens if all stocks have negative returns?

Answer: The "Best Performer" logic still works—it will pick the stock
that is "least negative" (e.g., -2% is "better" than -50%).
Mathematically, -2 > -50.

5. How would you handle the case where the "Investment" is 0 (e.g., a
free stock)?

Answer: Division by zero! (Current - 0) / 0 results in Infinity . I would need


to add a check: if (investedValue === 0) return 0; (or handle it as 100% gain)
to prevent the app from crashing or showing NaN .

6. If I have 10,000 stocks, reduce on the main thread might freeze the UI.
How would you fix this?

Answer: I would move the calculation to a Web Worker. This runs


the JavaScript on a background thread separate from the UI
rendering thread, keeping the dashboard responsive.

7. Why do you use an IIFE (Immediately Invoked Function Expression)


inside the JSX return?

Answer: React JSX doesn't allow standard if/else or variable


declarations ( const best = ... ) directly inside the template. Using {(() => { ...

})()}allows me to run complex logic, define variables, and return JSX


all within the render block.

8. What is the difference between map and forEach ?

Answer: map returns a new array of the same length (used here to
transform raw stocks into performance objects). forEach just loops
through and executes code but returns undefined (used for side
effects).

9. How does your code handle "Realized" vs "Unrealized" gains?

Answer: Currently, this feature only calculates Unrealized P&L


(gains on stocks strictly currently held). It does not look at stocks I

FINSIGHT TCS 41
have already sold. To support Realized P&L, I would need a
"Transaction History" model that tracks sold items.

10. Can reduce return an object instead of a number?

Answer: Yes! In my code, reduce returns the entire Stock Object


(ticker, quantity, price), not just the percentage number. This allows
me to display the Ticker Name and value in the UI card after finding
the winner.

Portfolio Health Meter


Here is the deep-dive breakdown for Feature 10: Portfolio Health Meter.
This feature visualizes the overall "health" of the user's investments using a
speedometer-style gauge. It provides an immediate, visual understanding
of whether the portfolio is profitable (Green zone) or losing money (Red
zone).

Feature 10: Portfolio Health Meter

1. The Proper Flow (Start to End)


This feature relies on client-side calculation to ensure it reflects the exact
live prices the user sees on their dashboard.

Step 1: Data Gathering (Frontend)

File: client/src/pages/[Link]

Prerequisite: The dashboard has already fetched the user's


Holdings (from the database) and the Live Prices (from the API).

State: These are stored in the stock array and the portfolioSummary

state object.

Step 2: The Math (Client-Side Logic)

File: client/src/pages/[Link]

Trigger: Inside the fetchStocksAndPrices function, after merging prices.

Aggregation: It loops through every stock to calculate two totals:

1. Total Invested (Sum of purchasePrice * quantity )

2. Current Value (Sum of livePrice * quantity )

The Metric: It calculates the overall percentage return:

FINSIGHT TCS 42
((CurrentValue - TotalInvested) / TotalInvested) * 100

Safety: It checks if (totalInvested > 0) to avoid "Division by Zero" errors


( NaN ).

Step 3: Component Configuration (Visualization)

File: client/src/pages/[Link]

Library: It uses react-d3-speedometer .

Normalization: The gauge is set to display a range from 50% to


+50%.

Clamping: The code uses [Link] and [Link] to ensure the needle
doesn't spin off the chart if the user has a +200% return. It caps the
visual needle at +50 (while the text still shows the real number).

Step 4: Rendering & Styling

File: client/src/pages/[Link]

Segments: The gauge is divided into 10 colored segments,


transitioning from Red (Loss) to Yellow (Neutral) to Green (Profit).

Dynamic Text: The center of the needle displays the exact


percentage (e.g., +12.5% ) dynamically.

2. Why We Used This (Technical Decisions)


Why use react-d3-speedometer instead of building it with CSS/SVG?

Reason: Complexity vs. Time. Building a mathematically accurate


gauge with proper needle rotation, segment coloring, and
animations using raw SVG/CSS is complex and error-prone. This
library wraps [Link] (the industry standard for data viz) into a simple
React component, saving hours of development time.

Why Clamp the Values (-50 to +50)?

Reason: Readability. If the range was -100 to +1000 to


accommodate crypto gains, a "normal" stock market return of +10%
would barely move the needle. Setting a realistic range of +/- 50%
makes the gauge sensitive enough to show daily movements while
still handling major trends.

Why Calculate on Frontend?

FINSIGHT TCS 43
Reason: Consistency. The "Total Value" card and the "Health
Meter" must match exactly. Since the "Total Value" is calculated on
the frontend (using live prices), the meter must use the exact same
source numbers. If we pre-calculated "Health" on the backend
using yesterday's closing price, the meter would disagree with the
live totals, confusing the user.

3. Important Interview Questions (Portfolio Health Meter)


1. How do you handle the "Division by Zero" edge case in your
percentage calculation?

Answer: In the fetchStocksAndPrices function, I explicitly check if

before dividing. If the user has 0 invested (empty


(totalInvested > 0)

portfolio), I set the percentage to 0 to prevent the application from


crashing or showing NaN .

2. Why did you choose react-d3-speedometer over other chart libraries like
[Link] (which you used for the line graph)?

Answer: [Link] is excellent for XY axes (Lines/Bars) and


Doughnuts, but its support for "Gauge/Speedometer" charts is not
native and requires complex plugins. react-d3-speedometer is a
specialized library built specifically for this visualization, offering
better out-of-the-box customization for needles and segments.

3. Explain the logic behind the "Clamping" of the needle value.

Answer: The gauge has a fixed visual scale (e.g., -50 to +50). If a
user has a 200% return, the library might throw an error or render
strangely. I used [Link]([Link](value, -50), 50) to strictly constrain the
needle's visual position within the bounds, while passing the real
value as the text label.

4. How does the color gradient work in this component?

Answer: The library accepts a segmentColors array. I manually defined


an array of hex codes ranging from Red ( #ef4444 ) to Yellow ( #eab308 )
to Green ( #22c55e ). The library automatically maps these colors to
the 10 segments of the arc.

5. If the API returns a price of 0 for a stock (error case), how does that
affect the meter?

FINSIGHT TCS 44
Answer: It would negatively impact the meter. A price of 0 means
the "Current Value" drops, causing a massive artificial loss. To fix
this in production, I should filter out invalid prices or fallback to the
purchasePrice to keep the meter neutral for that specific asset.

6. What is the "Needle Transition" and why is it important for UX?

Answer: I used needleTransition="easeElastic" with a duration of 1500ms.


This makes the needle bounce slightly when it settles, giving the UI
a polished, physical feel rather than just snapping instantly to the
number.

7. Is this a "Controlled" or "Uncontrolled" component?

Answer: It is a Controlled Component. Its value is strictly controlled


by the parent's state ( [Link] ). The gauge
cannot change its own value; it only reflects what React tells it to
display.

8. How would you make this component reusable?

Answer: Currently, it is hardcoded inside [Link] . To make it


reusable, I would extract it into a separate <HealthGauge value={percentage}
/> component, accepting the value and potential min/max ranges as

props.

9. Why use toFixed(1) for the label?

Answer: Floating point math often results in numbers like 12.399999% .


rounds this to 12.4% , which is cleaner for the UI and fits
toFixed(1)

better inside the gauge's limited text space.

10. Does this meter show "Realized" or "Unrealized" gains?

Answer: It shows strictly Unrealized Gains. It compares the current


market value of held stocks against their purchase cost. It does not
account for stocks that were already sold for a profit.

Stock Data and Analysis

Stock Search

FINSIGHT TCS 45
This feature is implemented entirely on the Frontend within the global
Header component, allowing access from any page.

Step 1: User Input (The Trigger)

File: client/src/components/[Link]

Action: The user types into the search input (e.g., "App").

State Update: The onChange handler updates the searchQuery state


variable immediately to reflect what is typed.

Step 2: The Traffic Controller (Debouncing)

File: client/src/components/[Link]

Problem: If the user types "Apple" fast, onChange fires 5 times (A,
Ap, App, Appl, Apple). Calling the API 5 times is wasteful and hits
rate limits.

Solution: The code uses a Debounce mechanism with setTimeout .

1. clearTimeout([Link]) : Cancels any pending search from the


previous keystroke.

2. [Link] = setTimeout(...) : Schedules a new search to happen


in 500ms.

Result: The API is only called after the user stops typing for half a
second.

Step 3: Fetching Suggestions (External API)

File: client/src/components/[Link]

Action: The fetchStockSuggestions function executes.

Request: It sends a GET request directly to [Link] (a third-


party market data provider).

Endpoint: [Link]

search=${query}&active=true&limit=10 .

Data: It asks for active tickers matching the search query, limited to
10 results.

Step 4: Processing & Rendering

File: client/src/components/[Link]

FINSIGHT TCS 46
Transformation: The API responds with a list. The code maps this
raw data into a clean structure: { symbol, name, exchange } .

State: It updates setSuggestions(...) and sets setShowSuggestions(true) .

UI: A dropdown list renders below the search bar, showing the
Ticker (bold) and Company Name.

Step 5: Selection & Navigation

File: client/src/components/[Link]

Action: The user clicks a suggestion.

Cleanup: The search bar is cleared, and the suggestions dropdown


is hidden.

Navigation: The app navigates to the Stock Insights page for that
ticker: Maps("/stock-insights/" + [Link]) .

2. Why We Used This (Technical Decisions)


Why use Debouncing?

Reason: Performance & Cost. External APIs often charge per


request or have strict limits (e.g., 5 requests/minute). Debouncing
ensures we don't waste quota on typos or intermediate keystrokes.

Why call [Link] directly from the Frontend?

Reason: Latency. Search needs to feel instant ("snappy"). Routing


the request through our backend ( Frontend -> Node Server -> Polygon -> Node
Server -> Frontend ) adds network hops and delay.

Critique: However, this exposes the API Key ( POLYGON_API_KEY ) in the


client-side code, which is a major security risk in production apps.

Why use useRef for the debounce timer?

Reason: State persistence without re-renders. We need to store


the timeoutIdso we can clear it on the next keystroke. Storing it in
useState would trigger unnecessary re-renders of the component
every time the user types, causing UI lag. useRef holds the ID
silently.

Why add a "Click Outside" listener?

FINSIGHT TCS 47
Reason: User Experience (UX). It is standard behavior for
dropdowns to close when you click away. The useEffect attaches a
mousedown listener to the document to detect clicks outside the search

container and close the dropdown.

3. Important Interview Questions (Stock Search)


1. What is "Debouncing" vs. "Throttling"? Which one did you use and
why?

Answer: Debouncing waits for a pause in execution (e.g., "Wait until


user stops typing"). Throttling limits execution to a fixed rate (e.g.,
"Fire once every 500ms max"). I used Debouncing because I only
care about the final search term, not the intermediate steps.

2. I see your API Key is hardcoded in [Link] . Why is this bad?

Answer: Anyone can "Inspect Element", go to the "Network" tab or


"Sources" tab, and steal my API key. They could then use my quota
for their own apps. In production, I should proxy this request
through my Backend ( /api/stock/search ) to keep the key hidden on the
server.

3. Explain how useRef helps in the debouncing logic.

Answer: useRef creates a mutable object ( .current ) that persists


across renders but doesn't trigger a re-render when changed. I use
it to store the setTimeout ID so I can cancel the previous timer
( clearTimeout ) even after the component re-renders due to the input
state change.

4. How would you handle "Race Conditions" in search? (e.g., Request A


takes 3s, Request B takes 1s)

Answer: If I type "App" (Req A) then "Apple" (Req B), and Req A
returns after Req B, the dropdown might show "App" results
(wrong). To fix this, I should ignore the results of Req A if the search
query has already changed, or use an AbortController to cancel the
stale request.

5. What is the "Cleanup Function" in useEffect and why is it used here?

Answer: In the useEffect that adds the "mousedown" listener (for


closing the dropdown), the return function removes that listener:

FINSIGHT TCS 48
[Link](...) . This prevents memory leaks and ensures
we don't have duplicate listeners if the component remounts.

6. Why use an external API (Polygon) instead of searching your own


database?

Answer: My database only contains stocks the user owns. The


search bar needs to find any stock in the market so the user can
add it or view insights. I cannot feasibly store all 50,000+ global
tickers in my own MongoDB.

7. How does the Maps function work in React Router?

Answer: It programmatically changes the browser's URL and pushes


a new entry to the history stack. This triggers React Router to
unmount the current page component and mount the new one
( StockInsights ) without a full page reload.

8. What is a "Controlled Component" in React?

Answer: The search input is a controlled component because its


value is controlled by React state ( value={searchQuery} ) and updated via
onChange . The "Source of Truth" is the React state, not the DOM.

9. If the API returns no results, how does the UI handle it?

Answer: The suggestions array will be empty. The code checks


before rendering the dropdown. I should ideally
[Link] > 0

add a "No results found" message to improve UX.

10. Why did you use autoComplete="off" on the input?

Answer: To prevent the browser's native autocomplete history from


covering up my custom suggestions dropdown. It keeps the UI
clean and ensures the user focuses on the live stock results.

Live Price Tracking

The Proper Flow (Start to End)


Step 1: The Request (Frontend)

File: client/src/pages/[Link]

Context: The dashboard has just fetched the user's holdings (e.g.,
AAPL, TSLA). It now needs to know: "What are these worth right
now?"

FINSIGHT TCS 49
Action: It extracts the list of tickers: const tickers = [Link](h => [Link]) .

API Call: It sends a GET request to ${API}/stock/last-price?tickers=AAPL,TSLA .

Step 2: The Route (Backend)

File: server/routes/[Link]

Action: The router directs the request to the getLastPrice controller.

Protection: verifyLogin ensures only authenticated users can request


price data.

Step 3: Database Lookup (Controller)

File: server/controllers/[Link]

Action: The getLastPrice function executes.

Parsing: It splits the query string tickers=AAPL,TSLA into an array.

The Loop: It iterates through each ticker.

Query: await [Link]({ ticker }) . It searches the StockPrice


collection, which acts as a historical cache.

Extraction: If found, it grabs the last entry from the values array:
[Link][[Link] - 1] . This represents the most recently
fetched price (Open, High, Low, Close).

Step 4: The Source of Data (Background Job)

Note: Since getLastPrice reads from the DB, how does data get there?

File: server/jobs/[Link]

Mechanism: A Cron Job runs automatically every day at 9:30 PM


EST.

Action:

1. It finds all unique tickers in the system.

2. It calls [Link](ticker) to fetch the real-time


price from the external API.

3. It pushes this new price into the StockPrice document's values

array.

Result: The dashboard displays the price as of the last Cron Job run
(or the last manual update).

FINSIGHT TCS 50
Step 5: Frontend Merge & Display

File: client/src/pages/[Link]

Action: The frontend receives the map: { "AAPL": { close: 150.00, ... }, "TSLA":

... } .

Merging: It maps over the holdings state. For each holding, it looks
up the price in this map.

livePrice = prices[[Link]].close

UI: The "Current Value" and "LTP" (Last Traded Price) columns are
updated with this figure.

2. Why We Used This (Technical Decisions)


Why read from Database instead of External API on every load?

Reason: Rate Limiting & Cost. External Finance APIs (like RapidAPI
or Yahoo Finance) charge per request or have strict limits (e.g., 500
requests/day). If you have 100 users refreshing their dashboard 10
times a day, you would hit 1,000 requests immediately. Caching
prices in MongoDB allows unlimited dashboard refreshes with zero
extra API cost.

Why use a Cron Job?

Reason: Automation. Users shouldn't have to click a "Update


Prices" button to see current values. A scheduled job ensures the
data is reasonably fresh (daily) without user intervention.

Why store prices in an Array ( values )?

Reason: History. By keeping an array of { date, price } , we


automatically build a historical dataset. This allows us to draw the
"Net Worth History" chart later without needing a separate
expensive API call for historical data.

Why do you have a getLivePrice endpoint if it's not used?

Reason: Future-Proofing / Debugging. server/controllers/[Link]

contains a getLivePrice function that does hit the API directly. It is


likely there for testing or for a future "Real-Time Mode" feature
where premium users could get up-to-the-second data.

FINSIGHT TCS 51
3. Important Interview Questions (Live Price Tracking)
1. What is the difference between Polling, WebSockets, and Server-Sent
Events (SSE)? Which is best for stock prices?

Answer:

Polling: Client asks server every X seconds (Simple, but high


latency/server load).

WebSockets: Two-way persistent connection (Best for real-


time, low latency like trading apps).

SSE: One-way server-to-client stream (Good for updates, easier


than WebSockets).

For FinSight: Currently, we use On-Demand Fetching (cached),


but for a real trading app, WebSockets would be the standard.

2. How do you handle API Rate Limits (HTTP 429)?

Answer: In the [Link] component, I explicitly check if ([Link] ===

429) . If hit, I stop making requests and display a user-friendly error


("Limit reached"). On the backend, I would implement a queue
system (like Redis/Bull) to throttle outgoing requests.

3. Explain the "Bucket Pattern" in MongoDB and why it's useful for time-
series data.

Answer: Instead of creating 1 document per price point (millions of


rows), we create 1 document per stock and store prices in an array
( values ). This reduces index size and makes reading a chunk of
history (e.g., "Last 100 days") extremely fast because it's a single
disk read.

4. Your Cron Job runs at 9:30 PM EST. What if the server crashes at
9:29 PM?

Answer: The job would be missed. To fix this, I would use a


persistent job queue (like [Link] or BullMQ) backed by
Redis/MongoDB. If the server restarts, the job queue would see the
missed task and run it immediately.

5. Why did you use [Link] instead of find ?

Answer: findOne returns the first matching document object directly.


find returns an array of documents (even if it's length 1). Since ticker

FINSIGHT TCS 52
is unique, I only expect one document, so findOne is cleaner and
slightly more efficient.

6. How would you implement "Real-Time" updates without refreshing


the page?

Answer: I would set up a setInterval in the React useEffect to call


every 60 seconds (Short Polling). Alternatively, I
fetchStocksAndPrices

could use [Link] to push updates from the server whenever the
Cron job finishes.

7. If the external API returns a price of null , how does your controller
handle it?

Answer: In [Link] , I check if (!close || close === 0) . If the price is


invalid, I log a warning and skip the update for that ticker to prevent
corrupting the database with bad data.

8. What is the Time Complexity of finding the latest price in the


getLastPrice controller?

Answer: It is O(1) (constant time) relative to the history length


because accessing the last element of an array by index ( length - 1 ) is
instant in JavaScript. The MongoDB query itself is O(log N) due to
the index on ticker .

9. Why do you verify the token on the /last-price route?

Answer: Even though it reads from cache, it is still data


consumption. Protecting it prevents unauthorized scraping of my
database and potential Denial of Service (DoS) attacks on my
internal APIs.

10. Explain how [Link] could optimize your getLivePrice (API version)
loop.

Answer: Currently, the loop uses await inside, which is sequential


(Wait for Apple... Then Wait for Tesla...). [Link] would fire all
requests simultaneously, reducing the total wait time to the duration
of the slowest single request.

AI Stock Insight

1. The Proper Flow (Start to End)


Step 1: User Navigation (Frontend)

FINSIGHT TCS 53
File: client/src/pages/[Link]

Trigger: The user clicks "View Insights" on a stock from their


portfolio or selects a stock from the Search Bar (e.g., "Tata
Motors").

Navigation: The app routes to /stock-insights/TATAMOTORS .

Request: The useEffect hook triggers immediately, calling


[Link]( API/stock−insights/{ticker} ) .

Step 2: Route Handling (Backend)

File: server/routes/[Link]

Action: The router receives GET /:ticker and directs it to


getStockInsightsController .

Step 3: The "Cache-First" Strategy (Service Layer)

File: server/services/[Link]

Action: The service getStockInsights is called.

Check: It first queries the MongoDB collection StockInsightsCache .

Logic: await [Link]({ ticker }) .

Hit: If a report exists and is less than 24 hours old, it returns the
cached report immediately. This makes the response instant
(milliseconds) instead of waiting for the AI (seconds).

Step 4: AI Generation (The "Miss" Scenario)

File: server/services/[Link]

Action: If no cache is found, we must generate fresh insights.

Prompt Engineering: The code constructs a massive text prompt:


"Analyze the stock ${ticker}... Return the response in strictly valid
JSON format with the following structure: { overview,
fundamental_analysis, ... }."

API Call: It sends this prompt to Google Gemini Pro via the
@google/generative-ai library: [Link](prompt) .

Step 5: Cleaning & Parsing

File: server/services/[Link]

FINSIGHT TCS 54
Problem: AI often wraps code in Markdown backticks (e.g., ```json
... ```).

Solution: The code cleans the string: [Link](/```json/g, "").replace(/```/g,

"") .

Parsing: It runs [Link](cleanText) to convert the AI's text response


into a usable JavaScript Object.

Step 6: Caching & Response

File: server/services/[Link]

Save: The new report is saved to MongoDB in StockInsightsCache so the


next user who asks for "Tata Motors" gets the fast version.

Return: The JSON object is sent back to the frontend.

Step 7: Rendering the Report (Frontend)

File: client/src/pages/[Link]

Visuals: The page renders multiple cards based on the JSON


structure:

Overview: Company description.

Fundamentals: Table of P/E, Market Cap, ROE.

Technical: Support/Resistance levels and Trend


(Bullish/Bearish).

Future Outlook: Bullet points of Risks vs. Opportunities.

Recommendation: A color-coded "Buy", "Hold", or "Sell"


badge.

2. Why We Used This (Technical Decisions)


Why Google Gemini?

Reason: Cost & Capability. Gemini Pro (at the time of development)
offered a generous free tier and high-quality reasoning capabilities
comparable to GPT-4 for structured tasks like JSON generation.

Why Cache in MongoDB instead of Redis?

Reason: Simplicity & Persistence. For a MERN stack project,


adding Redis adds infrastructure complexity (another server to

FINSIGHT TCS 55
manage). MongoDB works perfectly fine for caching simple text
documents, and we can inspect the cache easily using
Compass/Atlas.

Why "Strict JSON" Prompting?

Reason: Programmatic Access. We don't just want to display a wall


of text. We want to display the "P/E Ratio" in a specific table cell and
the "Trend" in a specific color. By forcing the AI to output JSON, the
frontend can destructure the data ( data.technical_analysis.trend ) and
render a custom UI.

Why a 24-Hour Cache TTL (Time To Live)?

Reason: Relevance vs. Rate Limits. Fundamental analysis (Outlook,


Competitors, Risks) rarely changes within a single day. Refreshing it
every minute would waste AI tokens. 24 hours is the sweet spot.

3. Important Interview Questions (AI Stock Insights)


1. How do you handle "AI Hallucinations" (AI generating fake numbers)?

Answer: In the prompt, I explicitly instructed the AI to act as a


"Financial Analyst" and provided the structure. However, there is
always a risk. In a production app, I would fetch the hard numbers
(Price, P/E) from a real API (Yahoo Finance) and feed them into the
prompt, asking the AI only to interpret them, rather than asking it to
find them.

2. What happens if the AI returns invalid JSON (e.g., a missing comma)?

Answer: The [Link]() will throw an error. My service wraps this in


a try/catch block. If parsing fails, I catch the error and return a
generic "Failed to analyze" message to the user instead of crashing
the server.

3. Explain "Prompt Engineering" in the context of your project.

Answer: Prompt Engineering is designing the input to the AI to


guarantee a specific output. I used "System Instructions" (telling it
to be a financial analyst) and "Format Constraints" (giving it a
dummy JSON skeleton) to ensure the output matches exactly what
my React frontend expects.

4. Why did you use replace(/```json/g, "") ?

FINSIGHT TCS 56
Answer: Large Language Models (LLMs) are trained to format code
blocks with Markdown. When asking for JSON, they often wrap the
result in triple backticks. [Link] cannot handle Markdown, so I
perform string manipulation to strip these markers before parsing.

5. How does the MongoDB "TTL Index" work for caching?

Answer: In the [Link] , I set expires: 86400 (24 hours) on


the createdAt field. MongoDB runs a background thread that
automatically deletes documents older than this time. This manages
cache eviction automatically without me writing cron jobs to delete
old data.

6. Can I force a refresh of the insights before 24 hours?

Answer: Yes. My controller accepts a query parameter ?refresh=true .


If present, the service ignores the cache and forces a new API call
to Gemini. This is useful for debugging or if significant news breaks.

7. What is the difference between [Link] and


[Link] ?

Answer: findById looks up a document by its primary _id . findOne can


look up by any field. Since I search by ticker (e.g., "AAPL") and not
the MongoDB ObjectId, I must use findOne .

8. How do you secure the Gemini API Key?

Answer: It is stored in the .env file as GEMINI_API_KEY and accessed


via [Link] . It is never exposed to the frontend; all AI requests
happen on the backend server.

9. If 10 users request "AAPL" insights at the exact same second, what


happens?

Answer: (Critique) Currently, my code might fire 10 simultaneous


requests to Gemini because the cache hasn't been written yet (Race
Condition). To fix this, I would implement a "Promise Map" or
"Request Deduping" in [Link] to collapse those 10 requests into a
single API call.

10. Why did you separate [Link] from [Link] ?

Answer: Separation of Concerns. The Controller handles the HTTP


request/response and error status codes. The Service handles the

FINSIGHT TCS 57
complex business logic (AI interaction, JSON cleaning, Cache
logic). This allows me to reuse the AI logic elsewhere (e.g., in a
background job) without mocking HTTP request objects.

News Feed

Step 1: Context Loading (Frontend)

File: client/src/pages/[Link]

Trigger: The user navigates to the News page.

Action: The useEffect hook fires fetchHoldings() to get the list of stocks
the user owns (e.g., AAPL, RELIANCE). Without this, we wouldn't
know which news to search for.

Step 2: Fetching News (Client-Side Aggregation)

File: client/src/pages/[Link]

Logic: The fetchNewsForHoldings function iterates through the user's


holdings.

API Call: For each stock, it sends a request directly to [Link]


(External API): [Link]

Caching Strategy: Before fetching, it checks localStorage . If news was


fetched < 10 minutes ago, it loads from cache to avoid hitting
Finnhub's strict rate limits.

Step 3: Sentiment Analysis Request (Backend Handoff)

File: client/src/pages/[Link]

Action: Once the news articles are collected, the frontend needs to
know: "Is this good news or bad news?"

Optimization: It creates a lightweight payload (Headlines +


Summaries only) and sends a POST request to ${API}/sentiment/analyze .

Step 4: Natural Language Processing (Backend Service)

File: server/services/[Link]

Library: The server uses the natural [Link] library.

Algorithm:

1. Tokenization: Splits headlines into individual words.

FINSIGHT TCS 58
2. Scoring: Uses the AFINN vocabulary (a list of words rated -5 to
+5) to calculate a raw score.

3. Financial Context: It checks for custom financial keywords


(defined in financialKeywords object) like "surge", "bullish" (Positive)
or "plummet", "bearish" (Negative).

4. Weighting: Final Score = (BasicScore * 0.4) + (FinancialScore * 0.4) +

(MarketScore * 0.2) .

Step 5: Response & Visualization

File: client/src/pages/[Link]

Backend: Returns an analysis object: { overallSentiment: "positive", confidence:

85, topArticles: [...] } .

Frontend:

Renders the News Feed as a list of cards.

Renders the Sentiment Dashboard (right sidebar) showing the


overall market mood and a breakdown (e.g., "Positive: 12,
Negative: 4").

2. Why We Used This (Technical Decisions)


Why fetch news on the Client instead of the Server?

Reason: Distributed API Quota. Finnhub has a free tier limit. If the
Server fetched news for all users, the server's single IP would get
banned instantly. By fetching from the Client (User's browser), each
user consumes their own "quota" (or the shared key's quota is
distributed across many IPs), effectively distributing the load.

Critique: This exposes the API Key ( d1rcav... ) in the frontend code,
which is a security risk.

Why use the natural library instead of an AI API (like Gemini) for
sentiment?

Reason: Speed & Cost. Sending 50 news headlines to


Gemini/OpenAI for analysis would take 5-10 seconds and cost
money per token. Using a local NLP library ( natural ) takes
milliseconds and is free. It's "good enough" for basic sentiment
scoring without the overhead of LLMs.

FINSIGHT TCS 59
Why Custom Financial Keywords?

Reason: Context Matters. Standard sentiment dictionaries might


think the word "crushing" is negative (e.g., "crushing hopes"). In
finance, "crushing earnings estimates" is extremely positive. We
added a custom dictionary to handle this domain-specific nuance.

Why Cache in LocalStorage?

Reason: Rate Limit Protection. Users tend to click back and forth
between pages. Re-fetching news every time they visit the "News"
tab would exhaust the API limit in seconds. A 10-minute cache
( CACHE_TIME ) ensures a smooth experience.

3. Important Interview Questions (News Feed)


1. I see you are calling [Link] directly from [Link] . What is the
security implication of this?

Answer: The API key is exposed to the client. A malicious user could
steal it and use it for their own app. In a production environment, I
should proxy this request through my own backend ( /api/news ),
where the key is stored securely in .env .

2. How does your Sentiment Analysis algorithm work? Is it AI?

Answer: It is not "Generative AI" (like GPT). It is a deterministic NLP


algorithm. It uses a "Bag of Words" approach with the AFINN
lexicon, combined with a custom dictionary of weighted financial
terms (e.g., "bullish", "shortfall") to calculate a composite score.

3. How do you handle the "N+1 Problem" when fetching news?

Answer: Currently, my code iterates through holdings and fires a


fetch request for each ticker. If a user has 20 stocks, that's 20
requests. This is inefficient. Ideally, I should look for an API endpoint
that accepts multiple tickers (e.g., symbols=AAPL,TSLA ) to fetch them in
a single batch.

4. Explain the logic behind the "Confidence Score" in your sentiment


analysis.

Answer: The confidence score is based on the consistency of the


sentiment. I calculate the percentage of articles that match the

FINSIGHT TCS 60
dominant sentiment. If 9 out of 10 articles are positive, confidence is
90%. If 5 are positive and 5 are negative, confidence is low (50%).

5. Why did you implement a "Skeleton Loader"?

Answer: The news API can be slow. Instead of showing a blank


white screen, I show a "Skeleton" (gray placeholder boxes) that
mimics the layout of the news cards. This improves Perceived
Performance, making the app feel faster.

6. What happens if the User has no holdings?

Answer: The fetchHoldings call returns an empty array. The code


checks if ([Link] === 0) and skips the news fetch. I display a "No
news found" or empty state to the user.

7. How would you implement "Infinite Scroll" for this news feed?

Answer: I would add a scroll event listener (or use IntersectionObserver ).


When the user reaches the bottom of the list, I would trigger a
function to fetch the next page of data from the API and append the
new articles to the existing newsList state array.

8. Why do you optimize the payload before sending articles to the


backend for analysis?

Answer: News objects contain full HTML bodies, images, and


metadata. Sending all that to the backend would be a huge payload
(slow). The sentiment analyzer only needs the headline and summary ,
so I create a stripped-down version of the array to save bandwidth.

9. What is the "Rate Limit" code for Finnhub?

Answer: HTTP 429. My code explicitly checks if ([Link] === 429) . If


true, it sets an error state telling the user the limit has been reached,
preventing the app from crashing.

10. Why do you assume "Bullish" is positive? Can't it be used negatively?

Answer: That is a limitation of "Bag of Words" NLP. It lacks context.


"Bullish on inflation" might be bad for the market, but my algorithm
would score "Bullish" as positive. This is why LLMs (like Gemini) are
better for nuanced analysis, but natural is faster for simple
dashboards.

Sentiment Analysis

FINSIGHT TCS 61
Step 1: Data Preparation (Frontend)

File: client/src/pages/[Link]

Context: The user has loaded the News page, and the app has
fetched 20-30 articles from the external news provider.

Optimization: The frontend strips away unnecessary data (images,


IDs) and creates a lightweight payload containing only headline ,
summary , and ticker .

Request: It sends a POST request to ${API}/sentiment/analyze with this


array of text.

Step 2: Validation (Backend Controller)

File: server/controllers/[Link]

Action: The analyzeNewsSentiment function receives the request.

Check: It validates that articles is an array. If empty, it returns a


neutral default response to prevent crashing the service.

Step 3: The Scoring Engine (Service Layer - Core Logic)

File: server/services/[Link]

Library: It initializes the natural SentimentAnalyzer using the AFINN


vocabulary (a standard list of English words rated from -5 to +5).

The Loop: For every article, it runs analyzeArticleSentiment(article) :

1. Text Merging: Combines headline + summary into one lowercase


string.

2. Base Score: Uses [Link](words) to get a generic English


sentiment score.

3. Financial Score: It checks a custom list of Financial Keywords


(e.g., "rally", "plunge", "bullish"). It calculates (PositiveMatches -

NegativeMatches) / TotalMatches .

4. Market Score: It checks for Market Indicators (e.g., "investor


fear", "market correction").

Weighted Formula:

Final Score = (Base * 0.4) + (Financial * 0.4) + (Market * 0.2)

FINSIGHT TCS 62
Why? This gives "Financial Terms" equal weight to "General
English," ensuring domain accuracy.

Step 4: Classification & Confidence

File: server/services/[Link]

Thresholds:

Score > 0.3 → Positive

Score < -0.3 → Negative

Otherwise → Neutral

Confidence: The absolute value of the score is converted to a


percentage (0-100%) to indicate how "strong" the sentiment is.

Step 5: Aggregation (The Summary)

File: server/services/[Link]

Action: After scoring all articles, it calculates the Overall Sentiment


of the entire batch.

Breakdown: It counts totals: { positive: 5, negative: 2, neutral: 10 } .

Top Picks: It sorts articles by their confidence score to identify the


"Most Significant News" (e.g., a report with a 90% confidence
score).

Step 6: Visualization (Frontend)

File: client/src/pages/[Link]

Action: The frontend receives the analysis data.

UI: It renders the "AI Sentiment Analysis" card.

Icon: A Green Smiley or Red Frown based on overallSentiment .

Stats: Displays the breakdown numbers.

Highlights: Lists the top 3 high-confidence articles so the user


reads the most impactful news first.

2. Why We Used This (Technical Decisions)


Why use the natural library instead of GPT/Gemini?

FINSIGHT TCS 63
Reason: Latency & Cost. Analyzing 50 articles with a Large
Language Model (LLM) is slow (5-10 seconds) and costs money per
token. natural runs locally on the CPU in milliseconds and is free. For
simple "Good/Bad" classification, it is highly efficient.

Why add Custom Financial Keywords?

Reason: Domain Specificity. In standard English, "crushing" is


negative. In finance, "crushing earnings" is positive. "Correction" in
standard English is neutral; in finance, it implies a drop. Our custom
dictionary overrides standard sentiment to fix these context errors.

Why use Weighted Scoring?

Reason: Precision. We trust our explicit financial keywords (weight


0.4) more than general English words (weight 0.4). By tuning these
weights, we can calibrate the algorithm to be more sensitive to
financial jargon.

Why analyze Headline + Summary?

Reason: Signal-to-Noise Ratio. Headlines are often clickbait ("You


won't believe what happened to Apple..."). The summary provides
the actual context. Analyzing both gives a more accurate reading
than the headline alone.

3. Important Interview Questions (Sentiment Analysis)


1. What is "Tokenization" in the context of your Sentiment Service?

Answer: Tokenization is the process of breaking a continuous string


of text ("Apple stock is rising") into individual units or "tokens"
( ["apple", "stock", "is", "rising"] ). The natural library does this automatically
so it can look up each word in its dictionary.

2. Explain the "Bag of Words" model. What is its limitation?

Answer: My algorithm treats the text as a "bag" of individual words,


ignoring grammar and order.

Limitation: It struggles with Negation. For example, "Not good"


might score positive because it sees "good", failing to understand
that "Not" flips the meaning. (Though advanced libraries try to
handle bigrams, natural 's basic sentiment analyzer is susceptible to
this).

FINSIGHT TCS 64
3. Why do you normalize the Confidence Score to 100?

Answer: The raw score from the algorithm might be 0.854 or 2.1 .
These numbers are meaningless to a user. Normalizing them to a 0-
100 scale makes the UI intuitive ("85% Confident").

4. How would you improve the accuracy of this without using an LLM?

Answer: I would implement Stemming. This reduces words to their


root form (e.g., "buying", "buys", "bought" -> "buy"). This ensures
that all variations of a word trigger the sentiment score. (Note: The
code does use PorterStemmer in initialization).

5. What is the Time Complexity of your analysis algorithm?

Answer: It is O(N * M), where N is the number of articles and M is


the average number of words per article. Since we iterate through
every word of every article once, it scales linearly with the amount
of text.

6. Why perform Sentiment Analysis on the Backend?

Answer: The natural library and its dictionaries are large. Bundling
them into the React frontend would increase the JavaScript bundle
size, slowing down the initial page load for the user. Running it on
the backend keeps the client lightweight.

7. How do you handle "Neutral" sentiment?

Answer: I use a threshold. If the calculated score is between 0.3

and +0.3 , I classify it as "Neutral". This prevents minor noise (like a


score of 0.05) from being falsely flagged as "Positive".

8. If a headline says "Apple fires 1000 employees", how does your


system catch that?

Answer: It relies on the [Link] array. If I included


words like "layoff", "fires", or "cut", the system detects them. If
those words are missing from my custom dictionary, the system
might miss the negative sentiment.

9. What is the difference between Lexicon-based (what you used) and


Machine Learning-based sentiment analysis?

Answer: Lexicon-based uses a pre-defined dictionary of "good"


and "bad" words (Rule-based). ML-based uses a trained model that

FINSIGHT TCS 65
"learns" patterns from thousands of examples. Lexicon is faster and
easier to build but less accurate with sarcasm or complex
sentences.

10. Why do you filter the request payload in [Link] before sending it?

Answer: To save bandwidth. The original news object from Finnhub


contains URLs, IDs, and image links. The sentiment analyzer only
needs text. Sending the full objects would waste network resources
for no reason.

System and Automation


Daily Cron Job

The Proper Flow (Start to End)


Step 1: The Schedule (Trigger)

File: server/jobs/[Link]

Library: Uses node-cron .

Timing: The schedule is set to 30 2 * * * .

In cron syntax, this is 02:30 UTC.

The code comment explains: 9:30 PM EST (New York time).


This is chosen because the US Stock Market closes at 4:00 PM
EST, so by 9:30 PM, all closing prices are final and settled.

Step 2: Identifying Targets (Optimization)

File: server/jobs/[Link]

Action: The job starts by querying the Holdings collection.

Query: [Link]().distinct("ticker") .

Why? If 1,000 users own "AAPL", we don't want to fetch Apple's


data 1,000 times. We find the unique list (e.g., ["AAPL", "TSLA", "MSFT"] )
and update each ticker exactly once.

Step 3: Fetching Fresh Data (External API)

File: server/jobs/[Link]

FINSIGHT TCS 66
Action: The job iterates through the unique tickers.

Service: For each ticker, it calls [Link](ticker) .

Data: It receives the latest Current Price, Market Cap, P/E Ratio,
and other metadata.

Step 4: Updating History (StockPrice Collection)

File: server/jobs/[Link]

Action: It pushes a new entry into the StockPrice document.

Data Structure:JavaScript
{
datetime: new Date(),
open: [Link],
high: [Link],
low: [Link],
close: [Link],
volume: 0
}

Upsert: It uses updateOne({ ticker }, { $push: ... }, { upsert: true }) . If the stock has
no history yet, it creates the document; otherwise, it appends to the
history array.

Step 5: Updating Metadata (Holdings Collection)

File: server/jobs/[Link]

Action: It updates every user's holding document that matches this


ticker.

Update: [Link]({ ticker }, { $set: { stockMetadata: metadata } }) .

Result: When users log in tomorrow, their "Sector Allocation" and


"Market Cap" charts will reflect any changes in the company's
valuation or classification.

Step 6: Error Handling & Reporting

File: server/jobs/[Link]

Action: The loop is wrapped in try/catch . If "Tesla" fails to update, the


loop continues to "Microsoft".

Logs: At the end, it prints a summary to the console: ✅ Successful: 50,


❌ Errors: 2, ⏱️ Duration: 5000ms . This helps developers debug issues.

FINSIGHT TCS 67
2. Why We Used This (Technical Decisions)
Why node-cron instead of setInterval ?

Reason: Precision. setInterval drifts over time (e.g., if the server


restarts, the timer resets). node-cron uses the system clock to run at
exact wall-clock times (e.g., "Exactly at 2:30 AM"), ensuring
reliability regardless of server uptime.

Why 9:30 PM EST?

Reason: Data Stability. Stock prices fluctuate wildly during trading


hours. "Closing Price" is the standard metric for tracking daily
performance. Waiting until well after market close ensures we
capture the official settlement price.

Why store price history in StockPrice and not Holdings ?

Reason: Normalization. As mentioned before, separating history


prevents massive data duplication. If we stored daily history inside
every user's holding, our database size would explode 1000x faster.

Why updateMany for Metadata?

Reason: Consistency. If Apple's P/E ratio changes, it changes for


everyone. Updating all holdings ensures that User A and User B
both see the same fundamental data for the same stock.

3. Important Interview Questions (Daily Cron Job)


1. What is a Cron Job? How does the syntax 30 2 * * * work?

Answer: A cron job is a time-based scheduler. The syntax


represents: Minute (30) Hour (2) DayOfMonth (*) Month (*) DayOfWeek (*) . So this
runs at 02:30 AM every day.

2. How would you scale this if you had 50,000 tickers to update?

Answer: A single loop would take too long (e.g., 50,000 seconds). I
would implement a Queue System (like BullMQ or RabbitMQ). The
cron job would just push 50,000 "Update Jobs" into the queue, and
multiple worker processes would pick them up and process them in
parallel.

3. What happens if the server restarts exactly at 9:30 PM?

FINSIGHT TCS 68
Answer: The cron job would be skipped for that day because node-

cron lives in the application memory. To fix this, I would use a

persistent job scheduler (like [Link] backed by MongoDB) that


checks for "missed" jobs upon startup and runs them.

4. Why do you verify ![Link] before saving?

Answer: To prevent data corruption. If the external API returns an


empty object or an error, saving a price of $0 would ruin the user's
charts (showing a 100% loss). I skip updates that lack valid price
data.

5. How do you handle Timezones in cron jobs?

Answer: node-cron allows a timezone option (e.g., timezone: "Asia/Kolkata" ).


However, it is best practice to schedule everything in UTC on the
server to avoid confusion with Daylight Savings Time changes.

6. What is the difference between updateMany and updateOne ?

Answer: modifies the first matching document it finds.


updateOne

updateMany modifies all documents that match the filter. I use


updateMany for Holdings because multiple users own the same stock,
and I need to update metadata for all of them.

7. Why is the upsert: true option important here?

Answer: It handles the "First Time" scenario. If a stock was added


today, it might not have a StockPrice history document yet. upsert: true

tells MongoDB: "Update it if it exists; otherwise, create a new


document." This saves me from writing separate if (exists) logic.

8. How do you prevent the Cron Job from blocking the main thread?

Answer: [Link] is single-threaded, but the I/O operations


(Database writes, API calls) are asynchronous. By using async/await ,
the event loop is free to handle other user requests (like
Login/Dashboard) while waiting for the database/API to respond.

9. What does .distinct("ticker") do?

Answer: It returns an array of unique values for the specified field. If


the collection has [{ticker: "AAPL"}, {ticker: "AAPL"}, {ticker: "TSLA"}] , it returns
["AAPL", "TSLA"] . This is crucial for deduplication.

10. Is there a risk of hitting API Rate Limits with this job?

FINSIGHT TCS 69
Answer: Yes. If I have 1,000 unique tickers, I make 1,000 API calls in
a loop. To fix this, I should add a sleep() or delay function inside the
loop (e.g., await new Promise(r => setTimeout(r, 100)) ) to space out the
requests.

Caching System
Here is the deep-dive breakdown for Feature 17: Caching System.
This feature is the "Performance Booster" of the application. It stores
expensive data (like AI reports and API responses) temporarily so the
system doesn't have to re-fetch it constantly. This saves money (API costs)
and makes the app feel instant for users.

Feature 17: Caching System

1. The Proper Flow (Start to End)


Step 1: The Request (Trigger)

File: server/controllers/[Link]

Scenario: A user visits the insights page for "NVIDIA" ( NVDA ).

Action: The controller receives GET /stock-insights/NVDA . It also checks


for a query parameter ?refresh=true (used if the user clicks a "Force
Refresh" button).

Step 2: The Cache Look-Up (Service Layer)

File: server/services/[Link]

Logic: The service getStockInsights runs.

Check: It runs [Link]({ ticker: "NVDA" }) .

Condition:

If refresh is true, it skips this step (forces new data).

If refresh is false, it waits for the database result.

Hit: If a document is found, it returns the stored data JSON


immediately. Time taken: ~50ms.

Miss: If no document is found (or refresh was true), it proceeds to


call the external API.

Step 3: Fetching Fresh Data (The Expensive Part)

FINSIGHT TCS 70
File: server/services/[Link]

Action: It calls the Google Gemini API (taking 3-5 seconds) to


generate the report.

Processing: It parses the AI's response into a clean JSON object.

Step 4: Writing to Cache (Storage)

File: server/services/[Link]

Action: Before returning the data, it saves it to MongoDB.

Upsert: It uses findOneAndUpdate with upsert: true .

Why Upsert? If a cache entry already existed (but we are refreshing


it), we overwrite it. If it didn't exist, we create it.

Result: The data is now stored for the next user.

Step 5: Automatic Expiration (The "Time-To-Live")

File: server/config/models/[Link]

Mechanism: The Mongoose schema defines a special


index:JavaScript
createdAt: { type: Date, default: [Link], expires: 86400 } // 24 hours

Background Process: MongoDB runs a background thread


(typically every 60 seconds). It looks for documents where createdAt

is older than 24 hours and automatically deletes them.

Result: We never have to manually delete old data. The cache self-
cleans.

2. Why We Used This (Technical Decisions)


Why use MongoDB for caching instead of Redis?

Reason: Simplicity. Redis is the industry standard for caching


because it stores data in RAM (super fast). However, for a MERN
portfolio project, adding Redis means setting up and paying for
another server/service. MongoDB is already connected, and reading
from disk is "fast enough" (milliseconds) compared to the AI API
(seconds).

Why use a TTL (Time-To-Live) Index?

FINSIGHT TCS 71
Reason: Self-Management. Without TTL, the StockInsightsCache

collection would grow infinitely until the database ran out of space.
TTL ensures that data is ephemeral (temporary) and irrelevant data
(old news) is discarded automatically.

Why 24 Hours?

Reason: Volatility vs. Cost. Stock fundamentals (P/E ratio, Market


Cap, Business Model) don't change every minute. They change
quarterly. Technical analysis changes daily. 24 hours is a balance: it
ensures the "Technical Outlook" isn't too stale while keeping API
calls low.

Why allow a refresh=true override?

Reason: User Control. Sometimes the cache is valid (e.g.,


generated 20 hours ago), but major news just happened now (e.g.,
earnings report). The user needs a way to say "I don't care about
the cache, get me the absolute latest info."

3. Important Interview Questions (Caching System)


1. What is a "Cache Hit" vs. a "Cache Miss"?

Answer:

Cache Hit: The data requested was found in the cache


(MongoDB), so we returned it instantly without calling the slow
external API.

Cache Miss: The data was NOT in the cache (or was expired),
so we had to fetch it from the source (Gemini) and then save it
to the cache.

2. How does MongoDB's expires property work? Is the deletion instant?

Answer: No, it is not instant. MongoDB runs a background thread


(usually every 60 seconds) to check for expired documents. So, a
document might theoretically live for 24 hours + 59 seconds before
disappearing. For our use case, this precision is acceptable.

3. Why didn't you cache the data in a JavaScript variable (e.g., let cache =

{} )?

Answer: Persistence & Memory.

FINSIGHT TCS 72
Persistence: If the server restarts (e.g., crashes or redeploys), a
JavaScript variable is wiped out. MongoDB persists the cache
across restarts.

Memory: Storing thousands of reports in RAM ([Link] heap)


would crash the server with an "Out of Memory" error.
MongoDB stores it on disk.

4. What is a "Cache Stampede" (or Thundering Herd)? Did you handle


it?

Answer: A cache stampede happens when 100 users request


"AAPL" at the exact same moment, and it's a "Cache Miss". The
server might fire 100 identical requests to Gemini before the first
one finishes and writes to the cache.

Critique: My current code does not handle this. To fix it, I would
store a "Pending Promise" in memory. If Request 1 is fetching
"AAPL", Request 2 should wait for Request 1's promise to resolve
instead of firing a new API call.

5. What is the difference between LRU Cache (Least Recently Used) and
TTL Cache ?

Answer:

TTL (Time-To-Live): Data dies after a fixed time (e.g., 24


hours), regardless of how popular it is. (This is what I used).

LRU: The cache has a fixed size (e.g., 100 items). When full, it
deletes the least recently accessed item to make room for new
ones. LRU is better for keeping popular items indefinitely.

6. Why use findOneAndUpdate instead of save ?

Answer: Atomic operations. If two requests try to update the cache


for "AAPL" simultaneously, findOneAndUpdate handles the lock better.
Also, with upsert: true , it handles both "Insert" (new) and "Update"
(refresh) logic in a single line of code.

7. How would you invalidate the cache manually if the data structure
changed?

Answer: I would simply drop the collection ( [Link]() )


or run a query to delete all documents: [Link]({}) .

FINSIGHT TCS 73
This forces a "fresh start" for all stocks.

8. Is this "Write-Through" or "Lazy Loading" (Write-Around) caching?

Answer: This is Lazy Loading (Cache-Aside). The application tries


to read from the cache first. If it misses, then it loads the data and
writes it to the cache. We don't pre-load the cache; it fills up only as
users request stocks.

9. What are the downsides of using MongoDB as a cache?

Answer: Latency. Reading from disk (MongoDB) is slower than


reading from RAM (Redis). Also, high read/write throughput on the
cache could slow down the main database, affecting critical
operations like User Login or Order placement.

10. Why do you store the ticker in the cache model?

Answer: It acts as the Key. In a Key-Value store (like caching), the


Ticker ("AAPL") is the unique identifier I use to retrieve the
associated value (the AI Report).

FINSIGHT TCS 74

You might also like