Comprehensive technical documentation for interview preparation
- What is InterviewJS?
- Why I Built This
- Tech Stack & Reasoning
- Architecture Deep Dive
- Project Structure
- Key Features Implementation
- Data Flow & State Management
- Authentication System
- Database Design
- API Routes
- Component Architecture
- Design Decisions & Trade-offs
- Common Interview Questions
InterviewJS is a full-stack web application designed to help developers master JavaScript, TypeScript, and related web technologies through interactive coding challenges and quizzes.
- 70+ curated coding problems across 11 different technologies
- Real-time code execution with Monaco Editor (VS Code's editor)
- Progress tracking persisted to MongoDB
- Gamified learning with solved/attempted/unsolved status tracking
- Quiz system for theoretical knowledge testing
| Frontend | Backend | Database |
|---|---|---|
| JavaScript | Node.js | MongoDB |
| TypeScript | Express | PostgreSQL (theory) |
| React | Next.js | Prisma (ORM) |
| HTML/CSS | - | - |
- Scattered learning resources - Interview prep content is fragmented across multiple platforms
- No personalized tracking - Hard to track what you've practiced
- Theoretical vs Practical gap - Most resources are theory-heavy, lacking hands-on coding
InterviewJS combines:
- Interactive code challenges with instant feedback
- Theory-based quizzes for conceptual understanding
- Personal progress dashboard showing strengths and weak areas
- All-in-one platform covering full-stack JavaScript ecosystem
- Frontend developers preparing for interviews
- Full-stack JavaScript/TypeScript developers
- Self-learners wanting structured practice
| Technology | Version | Why I Chose It |
|---|---|---|
| Next.js | 16.1.1 | App Router, Server Components, API Routes, SEO optimization, built-in routing |
| React | 19.2.3 | Latest features, concurrent rendering, improved performance |
| TypeScript | 5.9.3 | Type safety, better DX, catch errors at compile time |
| Tailwind CSS | 4.1.18 | Rapid UI development, utility-first, tree-shakable |
| Framer Motion | 12.23.27 | Smooth animations, gesture support, production-ready |
| Technology | Version | Why I Chose It |
|---|---|---|
| MongoDB | via Mongoose 9.1.1 | Flexible schema for varied problem structures, JSON-like documents |
| NextAuth.js | 4.24.13 | Seamless Next.js integration, multiple auth providers, session management |
| bcryptjs | 3.0.3 | Secure password hashing for email/password auth |
| Technology | Why I Chose It |
|---|---|
| shadcn/ui | Accessible, customizable, copy-paste components |
| Radix UI | Headless primitives for complex UI patterns |
| Monaco Editor | Same editor as VS Code, excellent DX |
| Lucide React | Consistent, customizable icons |
┌─────────────────────────────────────────────────────────────┐
│ CLIENT │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Pages │ │ Components │ │ Hooks │ │
│ │ (App Router)│ │ (React) │ │ (useChallenges)│ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
└────────────────────────────┬────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ API ROUTES │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ /auth │ │/problems │ │ /quiz │ │ /user │ │
│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │
└────────────────────────────┬────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ DATABASE │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ MongoDB │ │
│ │ ┌─────────┐ ┌─────────────────┐ ┌─────────────┐ │ │
│ │ │ User │ │ UserProblemData │ │UserAnswered │ │ │
│ │ │ │ │ │ │ Question │ │ │
│ │ └─────────┘ └─────────────────┘ └─────────────┘ │ │
│ └──────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
Static Problems (TypeScript files)
│
▼
useChallenges Hook ◄──── User Metadata (MongoDB)
│
▼
Merged Problem List
│
▼
React Components
Key Insight: Problems are defined statically in TypeScript files (source of truth) and overlaid with user-specific metadata (status, notes, stars) from MongoDB.
src/
├── app/ # Next.js App Router
│ ├── api/ # API Routes
│ │ ├── auth/ # NextAuth endpoints
│ │ │ ├── [...nextauth]/ # Dynamic auth handler
│ │ │ ├── register/ # User registration
│ │ │ └── user/ # User operations
│ │ ├── problems/ # Problem data API
│ │ ├── quiz/ # Quiz submission API
│ │ └── user/ # User profile & problem data
│ ├── practice/ # Practice pages
│ │ ├── [slug]/ # Dynamic topic pages
│ │ ├── js/ # JavaScript specific
│ │ └── ts/ # TypeScript specific
│ ├── profile/ # User profile page
│ ├── quiz/ # Quiz page
│ └── topics/ # Topics listing
│
├── components/ # React Components
│ ├── common/ # Shared components (Header, Footer)
│ ├── editor/ # Monaco Editor wrapper
│ ├── landing/ # Landing page sections
│ ├── modals/ # Modal components
│ ├── problems/ # Problem list/detail views
│ ├── profile/ # Profile page components
│ ├── progress/ # Progress tracking UI
│ ├── quiz/ # Quiz components
│ └── ui/ # shadcn/ui components
│
├── context/ # React Context
│ └── AuthContext.tsx # Authentication state
│
├── data/ # Static Data
│ ├── topics/ # Problem definitions (11 topics)
│ │ ├── javascript/ # JS problems (8 modules)
│ │ ├── typescript/ # TS problems (7 modules)
│ │ ├── react/ # React problems (7 modules)
│ │ ├── nextjs/ # Next.js problems
│ │ ├── nodejs/ # Node.js problems
│ │ ├── express/ # Express problems
│ │ ├── mongo/ # MongoDB problems
│ │ ├── prisma/ # Prisma problems
│ │ ├── postgres/ # PostgreSQL problems
│ │ ├── html/ # HTML problems
│ │ └── css/ # CSS problems
│ └── quizzes/ # Quiz questions
│
├── hooks/ # Custom React Hooks
│ └── useChallenges.ts # Main data/state hook
│
├── lib/ # Utilities
│ ├── auth/ # Auth utilities
│ ├── db/ # Database connection
│ └── utils/ # General utilities
│
├── models/ # Mongoose Models
│ ├── User.ts # User schema
│ ├── UserProblemData.ts # Problem progress
│ └── UserAnsweredQuestion.ts # Quiz answers
│
├── styles/ # Global styles
│ └── globals.css
│
└── types/ # TypeScript types
└── index.ts # Shared type definitions
Location: src/components/editor/
// Key features:
- Real-time code editing with syntax highlighting
- Language-aware (JS, TS, SQL based on topic)
- Custom toolbar (run, reset, copy)
- Console output panel
- Offline support
- Confetti animation on correct solutionWhy Monaco?
- Same engine as VS Code
- Rich IntelliSense support
- Battle-tested in production
Problem Definition (src/data/topics/javascript/problems/1-basics-1.ts):
export const learnTheBasics: Omit<Problem, 'status' | 'isStarred' | 'notes'>[] = [
{
id: 'unique-problem-id', // Unique identifier
title: 'Problem Title',
difficulty: Difficulty.Easy, // Easy | Medium | Hard
category: 'Basics', // Grouping category
group: 'Step 1: Learn the Basics', // UI section header
docsUrl: 'https://...', // Reference documentation
description: '...', // Markdown description
starterCode: '...', // Initial code template
testCases: [...], // Test inputs/outputs
solutionCheck: (userCode) => [...], // Validation function
}
];Key Design Decision: Problems are statically defined in TypeScript files, not stored in the database. This approach:
- ✅ Type-safe problem definitions
- ✅ No database migration needed for new problems
- ✅ Version controlled with Git
- ✅ Fast access (no network request)
The useChallenges Hook (src/hooks/useChallenges.ts):
export const useChallenges = (filter?: string) => {
// 1. Load static problems from TypeScript files
// 2. Fetch user metadata from API
// 3. Merge static + user data
// 4. Provide CRUD operations for user data
return {
problems, // Merged problem list
getProblemById, // Get single problem
isLoading, // Loading state
handleStatusChange, // Update solved/attempted status
handleToggleStar, // Star/unstar problem
handleUpdateNotes, // Add personal notes
};
};Location: src/components/modals/TopicModal.tsx
Features:
- Fullscreen mode (native Fullscreen API)
- Swipe gesture navigation (touch + trackpad)
- Keyboard navigation (arrow keys)
- Markdown rendering for answers
- Show/hide answer toggle
Flow:
- User takes quiz → answers stored in
UserAnsweredQuestion - Score calculated and displayed
- Detailed explanations shown for each question
- Progress tracked over time
| State Type | Solution | Why |
|---|---|---|
| Auth State | React Context + NextAuth | Global access, session-based |
| User Problem Data | React State + API | Server-side persistence |
| UI State | Component-local state | Simple, no prop drilling needed |
| Theme | next-themes | Built-in dark/light mode |
- Next.js App Router reduces need for global state
- React Context sufficient for auth state
- Server Components render with data directly
- SWR pattern in useChallenges provides caching
// useChallenges hook pattern
useEffect(() => {
const fetchProblems = async () => {
setIsLoading(true);
try {
const res = await fetch('/api/problems');
if (res.ok) {
const data = await res.json();
setUserProblemMetadata(data);
} else {
// Fallback to static defaults
setUserProblemMetadata(staticDefaults);
}
} finally {
setIsLoading(false);
}
};
if (!isAuthLoading) {
fetchProblems();
}
}, [isAuthenticated, isAuthLoading]);┌─────────────────────────────────────────┐
│ NextAuth.js │
│ ┌─────────────┐ ┌─────────────────┐ │
│ │ Google │ │ Credentials │ │
│ │ OAuth │ │ (Email/Pass) │ │
│ └─────────────┘ └─────────────────┘ │
│ │ │ │
│ └──────┬─────────┘ │
│ ▼ │
│ ┌───────────────┐ │
│ │ MongoDB User │ │
│ │ Collection │ │
│ └───────────────┘ │
└─────────────────────────────────────────┘
// src/context/AuthContext.tsx
export const AuthProvider = ({ children }) => {
const { data: session, status } = useSession();
const router = useRouter();
const value = {
user: session?.user ?? null,
isAuthenticated: !!session,
login: (provider) => signIn(provider),
logout: async () => {
await signOut();
router.push('/');
},
isLoading: status === 'loading',
};
return (
<AuthContext.Provider value={value}>
{children}
</AuthContext.Provider>
);
};// API route protection
export async function authMiddleware() {
const session = await getServerSession(authOptions);
if (!session?.user?.email) {
return null;
}
await connectDB();
return await User.findOne({ email: session.user.email });
}
// In API route
export async function GET() {
const user = await authMiddleware();
if (!user) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
// Continue with authorized logic...
}interface IUser {
username: string; // Required, unique
email: string; // Required, unique, lowercase
password?: string; // Optional (not for OAuth users)
authProvider?: string; // 'google', 'credentials', etc.
image?: string; // Profile picture URL
createdAt: Date; // Auto-generated
}interface IUserProblemData {
userId: ObjectId; // Reference to User
problemId: string; // References static problem ID
status: ProblemStatus; // 'Unsolved' | 'Solved' | 'Attempted'
isStarred: boolean; // Favorited by user
notes: string; // Personal notes
lastSubmittedAt?: Date; // Last submission time
submissionHistory: { // Historical submissions
timestamp: Date;
status: string;
}[];
}
// Compound unique index
UserProblemDataSchema.index({ userId: 1, problemId: 1 }, { unique: true });interface IUserAnsweredQuestion {
userId: ObjectId;
questionId: string; // References quiz question
selectedAnswer: string; // User's answer
isCorrect: boolean;
answeredAt: Date;
}- Flexible Schema: Problems have varied structures (different test cases, solution checks)
- JSON-like Documents: Natural fit for JavaScript ecosystem
- Easy Integration: Mongoose provides excellent TypeScript support
- Scalability: Horizontal scaling for future growth
// Ensures fast lookups and prevents duplicate entries
UserProblemDataSchema.index({ userId: 1, problemId: 1 }, { unique: true });/api
├── auth/
│ ├── [...nextauth]/route.ts # NextAuth dynamic handler
│ ├── register/route.ts # User registration
│ └── user/route.ts # Current user info
├── problems/
│ ├── route.ts # GET all problems (with user metadata)
│ └── [id]/route.ts # GET single problem
├── quiz/
│ └── submit/route.ts # POST quiz answers
└── user/
├── problem-data/route.ts # POST update problem status
└── profile/route.ts # GET/PUT user profile
// src/app/api/user/problem-data/route.ts
export async function POST(request: Request) {
const user = await authMiddleware();
if (!user) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
const body = await request.json();
const { problemId, status, isStarred, notes } = body;
// Upsert pattern - update if exists, create if not
const updatedData = await UserProblemData.findOneAndUpdate(
{ userId: user._id, problemId },
{
$set: {
...(status && { status }),
...(isStarred !== undefined && { isStarred }),
...(notes !== undefined && { notes }),
lastSubmittedAt: new Date(),
},
$push: {
submissionHistory: {
timestamp: new Date(),
status: status || 'update'
}
}
},
{ upsert: true, new: true }
);
return NextResponse.json(updatedData);
}App Layout
├── Header (common)
│ ├── Logo
│ ├── Navigation
│ ├── ThemeToggle
│ └── UserMenu
├── Main Content
│ ├── Landing Page
│ │ ├── Hero Section
│ │ ├── Features Section
│ │ └── CTA Section
│ ├── Practice Page
│ │ ├── TopicSelector
│ │ ├── ProblemList
│ │ │ └── ProblemCard (multiple)
│ │ └── ProblemDetail (modal/page)
│ │ ├── Description Panel
│ │ ├── CodeEditor
│ │ └── TestResults
│ ├── Quiz Page
│ │ ├── QuizQuestion
│ │ └── QuizResults
│ └── Profile Page
│ ├── UserStats
│ ├── ActivityGraph
│ └── SubmissionHistory
└── Footer (common)
Using shadcn/ui components stored in src/components/ui/:
| Component | Usage |
|---|---|
Button |
All interactive buttons |
Card |
Problem cards, feature cards |
Dialog |
Modals, confirmations |
Tabs |
Topic switching |
Progress |
Progress bars |
Select |
Difficulty filter |
ScrollArea |
Scrollable containers |
Tooltip |
Helpful hints |
Decision: Store problems in TypeScript files, not MongoDB
| Pros | Cons |
|---|---|
| ✅ Type-safe definitions | ❌ Cannot add problems via UI |
| ✅ Version control with Git | ❌ Requires deploy for new problems |
| ✅ No database migration | ❌ No A/B testing on problems |
| ✅ Instant loading | |
| ✅ Works offline |
Decision: MongoDB with Mongoose
| MongoDB | PostgreSQL |
|---|---|
| ✅ Flexible schema | ✅ Strong relations |
| ✅ JSON documents | ✅ ACID transactions |
| ✅ Easy to start | ❌ Schema migrations |
| ✅ Great for prototyping | ❌ More setup overhead |
Why MongoDB wins here: Problem structures vary (different test cases, solution validators). Document model fits better than rigid tables.
Decision: App Router (Next.js 15+)
| App Router | Pages Router |
|---|---|
| ✅ Server Components | ❌ All client-side by default |
| ✅ Simpler data fetching | ❌ getServerSideProps/getStaticProps |
| ✅ Nested layouts | ❌ Flat structure |
| ✅ Future of Next.js | ❌ Legacy approach |
Decision: NextAuth with multiple providers
| Approach | Why |
|---|---|
| Google OAuth | Most users prefer social login |
| Email/Password | Fallback for users without Google |
| JWT Sessions | Stateless, scalable |
Q: Why store problems in TypeScript files instead of database?
- Type Safety: Problem definitions are type-checked at compile time
- Version Control: Changes tracked in Git with PR reviews
- No Migration: Adding problems doesn't require DB schema changes
- Performance: No network request to load problem definitions
- Trade-off: Can't add problems via UI (acceptable for this use case)
Q: How does the code execution work?
Code is executed client-side in a sandboxed environment:
- User writes code in Monaco Editor
- Click "Run" triggers
solutionCheck()function- Function evaluates user code against test cases
- Results displayed with pass/fail status
Q: Explain your authentication flow
- User clicks "Sign in with Google" or submits credentials
- NextAuth handles OAuth flow / credential validation
- On success, session created with JWT
AuthContextwraps app, exposinguseAuth()hook- Protected routes check
isAuthenticatedbefore rendering
Q: How do you persist user progress?
Overlay Pattern:
- Static problems loaded from TypeScript files
- User metadata fetched from MongoDB (
UserProblemData)useChallengeshook merges both datasets- User actions (solve, star, note) → POST to API
- MongoDB upserts with compound index
(userId, problemId)
Q: How do you optimize initial load performance?
- Server Components: Default in App Router, reduces JS bundle
- Code Splitting: Dynamic imports for editor (
next/dynamic)- Image Optimization: Next.js
<Image>with lazy loading- Tailwind Tree-shaking: Unused CSS removed in production
Q: How would you scale this application?
- Database: MongoDB can shard horizontally
- Caching: Add Redis for session/API caching
- CDN: Static assets on Vercel Edge/Cloudflare
- API Rate Limiting: Protect endpoints from abuse
- Read Replicas: If read-heavy, add MongoDB replicas
Q: How do you ensure type safety?
- Strict TypeScript config:
strict: true- Shared Types: Central
types/index.ts- Zod Validation: Runtime validation for API inputs
- Mongoose TypeScript: Typed model interfaces
Q: Walk me through the Problem type
interface Problem {
id: string; // Unique identifier
title: string; // Display name
description: string; // Markdown description
difficulty: Difficulty; // Enum: Easy | Medium | Hard
category: string; // Grouping (e.g., "Basics")
group: string; // UI section header
docsUrl: string; // Reference link
starterCode: string; // Initial code template
testCases: TestCase[]; // Input/output pairs
solutionCheck: Function; // Validation logic
// User-specific fields (from DB overlay):
status: ProblemStatus; // Unsolved | Solved | Attempted
isStarred?: boolean; // User favorited
notes?: string; // User notes
slug?: string; // Topic identifier (js, ts, etc.)
}git clone https://github.com/shridmishra/interviewjs.git
cd interviewjs
npm install
# Create .env.local with MONGO_URI, NEXTAUTH_SECRET
npm run dev| File | Purpose |
|---|---|
src/hooks/useChallenges.ts |
Core data hook |
src/context/AuthContext.tsx |
Auth state |
src/app/api/auth/[...nextauth]/route.ts |
Auth config |
src/models/*.ts |
Database schemas |
src/data/topics/*/problems/*.ts |
Problem definitions |
src/components/editor/ |
Code editor |
MONGO_URI=mongodb://... # MongoDB connection
NEXTAUTH_SECRET=xxx # JWT signing secret
NEXTAUTH_URL=http://localhost:3000
GOOGLE_CLIENT_ID=xxx # OAuth (optional)
GOOGLE_CLIENT_SECRET=xxx # OAuth (optional)Last Updated: January 2026
Author: Shrid Mishra
Project: InterviewJS