Repository: seanprashad/leetcode-patterns
Branch: main
Commit: 5c24b6a9272d
Files: 72
Total size: 519.1 KB
Directory structure:
gitextract_d7fndcjj/
├── .github/
│ ├── ISSUE_TEMPLATE/
│ │ ├── bug_report.yml
│ │ ├── feature_request.yml
│ │ └── question_request.yml
│ └── workflows/
│ ├── ci.yml
│ ├── deploy.yml
│ └── update-questions.yml
├── .gitignore
├── .husky/
│ └── pre-push
├── .npmrc
├── LICENSE
├── README.md
├── cron/
│ ├── leetcode/
│ │ ├── __init__.py
│ │ ├── auth.py
│ │ ├── models.py
│ │ └── rest.py
│ └── update_questions.py
├── eslint.config.mjs
├── next.config.ts
├── package.json
├── postcss.config.mjs
├── public/
│ ├── .nojekyll
│ ├── manifest.json
│ ├── robots.txt
│ └── sw.js
├── scripts/
│ └── generate-sw-precache.mjs
├── src/
│ ├── app/
│ │ ├── globals.css
│ │ ├── layout.tsx
│ │ ├── not-found.tsx
│ │ └── page.tsx
│ ├── components/
│ │ ├── layout/
│ │ │ ├── AuthContext.test.tsx
│ │ │ ├── AuthContext.tsx
│ │ │ ├── GitHubLink.tsx
│ │ │ ├── Logo.tsx
│ │ │ ├── ServiceWorkerRegistrar.tsx
│ │ │ ├── ThemeToggle.test.tsx
│ │ │ ├── ThemeToggle.tsx
│ │ │ ├── UserMenu.test.tsx
│ │ │ ├── UserMenu.tsx
│ │ │ └── ViewSwitcher.tsx
│ │ ├── panels/
│ │ │ ├── AboutPanel.tsx
│ │ │ ├── AcknowledgementsPanel.tsx
│ │ │ ├── TipsPanel.tsx
│ │ │ └── panels.test.tsx
│ │ ├── questions/
│ │ │ ├── ConfirmModal.tsx
│ │ │ ├── FilterToolbar.tsx
│ │ │ ├── GroupHeaderRow.tsx
│ │ │ ├── NoteModal.tsx
│ │ │ ├── ProgressBar.tsx
│ │ │ ├── QuestionRow.tsx
│ │ │ ├── QuestionsTable.test.tsx
│ │ │ ├── QuestionsTable.tsx
│ │ │ └── ReviewDateModal.tsx
│ │ └── roadmaps/
│ │ ├── RoadmapView.test.tsx
│ │ └── RoadmapView.tsx
│ ├── data/
│ │ ├── questions.json
│ │ └── roadmaps.ts
│ ├── lib/
│ │ ├── analytics.test.ts
│ │ ├── analytics.ts
│ │ ├── register-sw.test.ts
│ │ ├── register-sw.ts
│ │ ├── reminders.test.ts
│ │ ├── reminders.ts
│ │ ├── storage.test.ts
│ │ ├── storage.ts
│ │ ├── supabase.ts
│ │ ├── sw.test.ts
│ │ ├── sync.test.ts
│ │ └── sync.ts
│ ├── test/
│ │ └── setup.ts
│ └── types/
│ └── question.ts
├── tsconfig.json
└── vitest.config.mts
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.yml
================================================
name: Bug Report
description: Report a bug or issue with Leetcode Patterns
labels: ["bug"]
body:
- type: textarea
id: description
attributes:
label: Description
description: A clear description of the bug.
placeholder: Describe the issue...
validations:
required: true
- type: textarea
id: steps
attributes:
label: Steps to Reproduce
description: How can we reproduce this issue?
placeholder: |
1. Go to '...'
2. Click on '...'
3. See error
validations:
required: true
- type: textarea
id: expected
attributes:
label: Expected Behavior
description: What did you expect to happen?
validations:
required: true
- type: textarea
id: screenshots
attributes:
label: Screenshots
description: If applicable, add screenshots to help explain the issue.
validations:
required: false
- type: dropdown
id: browser
attributes:
label: Browser
options:
- Chrome
- Firefox
- Safari
- Edge
- Other
validations:
required: false
================================================
FILE: .github/ISSUE_TEMPLATE/feature_request.yml
================================================
name: Feature Request
description: Suggest a new feature or improvement
labels: ["enhancement"]
body:
- type: textarea
id: description
attributes:
label: Description
description: A clear description of the feature you'd like.
placeholder: I'd like to see...
validations:
required: true
- type: textarea
id: motivation
attributes:
label: Motivation
description: Why would this feature be useful?
validations:
required: false
- type: textarea
id: alternatives
attributes:
label: Alternatives Considered
description: Any alternative solutions or features you've considered.
validations:
required: false
================================================
FILE: .github/ISSUE_TEMPLATE/question_request.yml
================================================
name: Question Request
description: Request a new question to be added to the list
labels: ["question-request"]
body:
- type: input
id: title
attributes:
label: Question Title
description: The title of the LeetCode question.
placeholder: e.g. Two Sum
validations:
required: true
- type: input
id: url
attributes:
label: LeetCode URL
description: Link to the question on LeetCode.
placeholder: https://leetcode.com/problems/two-sum/
validations:
required: true
- type: dropdown
id: difficulty
attributes:
label: Difficulty
options:
- Easy
- Medium
- Hard
validations:
required: true
- type: input
id: pattern
attributes:
label: Pattern(s)
description: Which pattern(s) does this question belong to?
placeholder: e.g. Arrays, Two Pointers
validations:
required: true
- type: textarea
id: reason
attributes:
label: Why should this be added?
description: Why is this question a good fit for the list?
validations:
required: false
================================================
FILE: .github/workflows/ci.yml
================================================
name: CI
on:
pull_request:
types: [opened, synchronize, reopened, closed, labeled]
branches: [main]
permissions:
contents: write
pull-requests: write
jobs:
lint:
if: github.event.action != 'closed'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
- run: npm ci
- run: npm run lint
test:
if: github.event.action != 'closed'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
- run: npm ci
- run: npx vitest run --coverage
preview:
if: >-
always() &&
contains(github.event.pull_request.labels.*.name, 'preview') &&
(github.event.action == 'closed' || (needs.lint.result == 'success' && needs.test.result == 'success'))
needs: [lint, test]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- if: github.event.action != 'closed'
uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
- if: github.event.action != 'closed'
run: npm ci
- if: github.event.action != 'closed'
name: Build
env:
NEXT_PUBLIC_BASE_PATH: /leetcode-patterns/pr-preview/pr-${{ github.event.number }}
NEXT_PUBLIC_SUPABASE_URL: ${{ secrets.NEXT_PUBLIC_SUPABASE_URL }}
NEXT_PUBLIC_SUPABASE_ANON_KEY: ${{ secrets.NEXT_PUBLIC_SUPABASE_ANON_KEY }}
run: npx next build
- if: github.event.action != 'closed'
name: Remove service worker from preview
run: rm -f out/sw.js
- uses: rossjrw/pr-preview-action@v1
with:
source-dir: out
================================================
FILE: .github/workflows/deploy.yml
================================================
name: Deploy to GitHub Pages
on:
push:
branches: [main]
workflow_run:
workflows: [Update Questions]
types:
- completed
workflow_dispatch:
permissions:
contents: write
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
- run: npm ci
- name: Build
env:
NEXT_PUBLIC_BASE_PATH: /leetcode-patterns
NEXT_PUBLIC_SUPABASE_URL: ${{ secrets.NEXT_PUBLIC_SUPABASE_URL }}
NEXT_PUBLIC_SUPABASE_ANON_KEY: ${{ secrets.NEXT_PUBLIC_SUPABASE_ANON_KEY }}
run: npm run build
- uses: JamesIves/github-pages-deploy-action@v4
with:
folder: out
branch: gh-pages
clean-exclude: pr-preview
force: false
================================================
FILE: .github/workflows/update-questions.yml
================================================
name: Update Questions
on:
schedule:
# Every Sunday at 8am EST (1pm UTC)
- cron: "0 13 * * 0"
workflow_dispatch:
permissions:
contents: write
jobs:
update:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Update question metadata
working-directory: cron
env:
LEETCODE_SESSION_TOKEN: ${{ secrets.LEETCODE_SESSION_TOKEN }}
LEETCODE_CSRF_TOKEN: ${{ secrets.LEETCODE_CSRF_TOKEN }}
LEETCODE_CF_CLEARANCE: ${{ secrets.LEETCODE_CF_CLEARANCE }}
run: python update_questions.py
- name: Commit and push changes
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add src/data/questions.json
git diff --cached --quiet || git commit --author="Sean Prashad <13009507+seanprashad@users.noreply.github.com>" -m "chore: update question metadata" && git push
================================================
FILE: .gitignore
================================================
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.*
.yarn/*
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/versions
# testing
/coverage
# next.js
/.next/
/out/
# production
/build
# misc
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*
# env files (can opt-in for committing if needed)
.env*
# vercel
.vercel
# typescript
*.tsbuildinfo
next-env.d.ts
# python
__pycache__
================================================
FILE: .husky/pre-push
================================================
npm run lint -- --fix
npm test
================================================
FILE: .npmrc
================================================
registry=https://registry.npmjs.org
================================================
FILE: LICENSE
================================================
Creative Commons Attribution-NonCommercial 4.0 International
Copyright (c) 2026 Sean Prashad
This work is licensed under the Creative Commons
Attribution-NonCommercial 4.0 International License.
You are free to:
Share — copy and redistribute the material in any medium or format
Adapt — remix, transform, and build upon the material
Under the following terms:
Attribution — You must give appropriate credit, provide a link to
the license, and indicate if changes were made. You may do so in
any reasonable manner, but not in any way that suggests the licensor
endorses you or your use.
NonCommercial — You may not use the material for commercial purposes.
No additional restrictions — You may not apply legal terms or
technological measures that legally restrict others from doing
anything the license permits.
Notices:
You do not have to comply with the license for elements of the
material in the public domain or where your use is permitted by an
applicable exception or limitation.
No warranties are given. The license may not give you all of the
permissions necessary for your intended use. For example, other
rights such as publicity, privacy, or moral rights may limit how
you use the material.
Full license text:
https://creativecommons.org/licenses/by-nc/4.0/legalcode
================================================
FILE: README.md
================================================
## Table of Contents
- [Background](#background)
- [Fundamentals](#fundamentals)
- [Notes](#notes)
- [Question List](#question-list)
- [Solutions](#solutions)
- [Contributing](#contributing)
- [Suggestions](#suggestions)
- [Acknowledgements](#acknowledgements)
## Background
This repo is intended for any individual wanting to improve their problem
solving skills for software engineering interviews.
Problems are grouped under their respective subtopic, in order to focus on
repeatedly applying common patterns rather than randomly tackling questions.
All questions are available on [leetcode.com] with some requiring [leetcode premium].
## Fundamentals
To find the greatest amount of success when practicing, it is highly recommended
to know the methods and runtimes of the following data structures and their
operations:
- Arrays
- Maps
- Linked Lists
- Queues
- Heaps
- Stacks
- Trees
- Graphs
In addition, you should have a good grasp on common algorithms such as:
- Breadth-first search
- Depth-first search
- Binary search
- Recursion
## Notes
[This pdf] contains information for the main data structures in Java.
Other useful methods to know include [`substring()`](https://docs.oracle.com/javase/8/docs/api/java/lang/String.html#substring-int-int-), [`toCharArray()`](https://docs.oracle.com/javase/8/docs/api/java/lang/String.html#toCharArray--), [`Math.max()`](https://docs.oracle.com/javase/8/docs/api/java/lang/Math.html#max-int-int-),
[`Math.min()`](https://docs.oracle.com/javase/8/docs/api/java/lang/Math.html#min-int-int-), and [`Arrays.fill()`](https://docs.oracle.com/javase/8/docs/api/java/util/Arrays.html#fill-int:A-int-).
## Question List
The entire question list can be found here:
https://seanprashad.com/leetcode-patterns/.
## Solutions
Solutions written in Java can be found in the [solutions] branch.
## Contributing
The app is built with [Next.js] (App Router), [React] 19, [TypeScript], [Tailwind CSS] v4, [TanStack Table] v8, [Lucide React] for icons, and Google Analytics via `@next/third-parties`. Tests use [Vitest] + [React Testing Library].
```bash
npm install
npm run dev # http://localhost:3000
npm test # single run
npm run test:watch # watch mode
```
A [Husky] `pre-push` hook runs `npm test` automatically before every push. This is set up for every clone via the `prepare` script.
## Acknowledgements
This list is heavily inspired from [Grokking the Coding Interview] with
additional problems extracted from the [Blind 75 list] and this hackernoon article
on [14 patterns to ace any coding interview question].
[leetcode.com]: https://leetcode.com
[leetcode premium]: https://leetcode.com/subscribe/
[next.js]: https://nextjs.org
[react]: https://react.dev
[typescript]: https://www.typescriptlang.org
[tailwind css]: https://tailwindcss.com
[tanstack table]: https://tanstack.com/table
[lucide react]: https://lucide.dev
[vitest]: https://vitest.dev
[react testing library]: https://testing-library.com/docs/react-testing-library/intro
[husky]: https://typicode.github.io/husky
[this pdf]: https://drive.google.com/open?id=1ao4ZA28zzBttDkuS6MLQI52gDs_CJZEm
[solutions]: https://github.com/SeanPrashad/leetcode-patterns/tree/solutions
[grokking the coding interview]: https://www.educative.io/courses/grokking-the-coding-interview
[issue]: https://github.com/SeanPrashad/leetcode-patterns/issues/new
[blind 75 list]: https://www.teamblind.com/article/New-Year-Gift---Curated-List-of-Top-100-LeetCode-Questions-to-Save-Your-Time-OaM1orEU?utm_source=share&utm_medium=ios_app
[14 patterns to ace any coding interview question]: https://hackernoon.com/14-patterns-to-ace-any-coding-interview-question-c5bb3357f6ed
================================================
FILE: cron/leetcode/__init__.py
================================================
from leetcode.models import (
Configuration,
ApiClient,
DefaultApi,
GraphqlQuery,
GraphqlQueryGetQuestionDetailVariables,
GraphqlQuestionDetail,
GraphqlData,
GraphqlResponse,
)
from leetcode import auth
from leetcode import rest
__all__ = [
"Configuration",
"ApiClient",
"DefaultApi",
"GraphqlQuery",
"GraphqlQueryGetQuestionDetailVariables",
"GraphqlQuestionDetail",
"GraphqlData",
"GraphqlResponse",
"auth",
"rest",
]
================================================
FILE: cron/leetcode/auth.py
================================================
# Placeholder for leetcode.auth compatibility.
# Authentication is handled via Configuration.api_key in the main module.
================================================
FILE: cron/leetcode/models.py
================================================
import json
import urllib.request
from leetcode.rest import ApiException
class Configuration:
def __init__(self):
self.host = "https://leetcode.com"
self.api_key = {}
self.debug = False
class GraphqlQuery:
def __init__(self, query=None, variables=None, operation_name=None):
self.query = query
self.variables = variables
self.operation_name = operation_name
class GraphqlQueryGetQuestionDetailVariables:
def __init__(self, title_slug=None):
self.title_slug = title_slug
class GraphqlQuestionDetail:
def __init__(self, question_id=None, title=None, difficulty=None,
company_tag_stats_v2=None, is_paid_only=None, topic_tags=None):
self.question_id = question_id
self.title = title
self.difficulty = difficulty
self.company_tag_stats_v2 = company_tag_stats_v2
self.is_paid_only = is_paid_only
self.topic_tags = topic_tags
class GraphqlData:
def __init__(self, question=None):
self.question = question
class GraphqlResponse:
def __init__(self, data=None):
self.data = data
class ApiClient:
def __init__(self, configuration=None):
self.configuration = configuration or Configuration()
class DefaultApi:
def __init__(self, api_client=None):
self.api_client = api_client or ApiClient()
def graphql_post(self, body=None):
config = self.api_client.configuration
variables = {}
if body.variables and hasattr(body.variables, "title_slug"):
variables["titleSlug"] = body.variables.title_slug
payload = {"query": body.query, "variables": variables}
if body.operation_name:
payload["operationName"] = body.operation_name
data = json.dumps(payload).encode("utf-8")
cookie = (
f"csrftoken={config.api_key.get('csrftoken') or ''}; "
f"LEETCODE_SESSION={config.api_key.get('LEETCODE_SESSION') or ''}; "
f"cf_clearance={config.api_key.get('cf_clearance') or ''}"
)
url = config.host + "/graphql"
req = urllib.request.Request(url, data=data, method="POST")
req.add_header("Content-Type", "application/json")
req.add_header("Accept", "application/json")
req.add_header("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36")
req.add_header("x-csrftoken", config.api_key.get("x-csrftoken") or "")
req.add_header("Referer", config.api_key.get("Referer") or "https://leetcode.com")
req.add_header("Cookie", cookie)
try:
with urllib.request.urlopen(req) as resp:
resp_data = json.loads(resp.read().decode("utf-8"))
except urllib.error.HTTPError as e:
raise ApiException(
status=e.code,
reason=e.reason,
body=e.read().decode("utf-8"),
)
question_data = resp_data.get("data", {}).get("question", {})
topic_tags_raw = question_data.get("topicTags")
topic_tags = [type("Tag", (), {"name": t["name"]}) for t in topic_tags_raw]
question = GraphqlQuestionDetail(
question_id=question_data.get("questionId"),
title=question_data.get("title"),
difficulty=question_data.get("difficulty"),
company_tag_stats_v2=question_data.get("companyTagStatsV2"),
is_paid_only=question_data.get("isPaidOnly"),
topic_tags=topic_tags,
)
return GraphqlResponse(data=GraphqlData(question=question))
================================================
FILE: cron/leetcode/rest.py
================================================
class ApiException(Exception):
def __init__(self, status=None, reason=None, body=None):
self.status = status
self.reason = reason
self.body = body
def __str__(self):
msg = f"({self.status})\nReason: {self.reason}\n"
if self.body:
msg += f"HTTP response body: {self.body}\n"
return msg
================================================
FILE: cron/update_questions.py
================================================
import os
import json
import leetcode
import leetcode.auth
from datetime import datetime
from leetcode.rest import ApiException
def create_leetcode_api():
leetcode_session_token = os.environ.get("LEETCODE_SESSION_TOKEN")
csrf_token = os.environ.get("LEETCODE_CSRF_TOKEN")
cf_clearance = os.environ.get("LEETCODE_CF_CLEARANCE")
if not leetcode_session_token:
print("❌ LEETCODE_SESSION_TOKEN environment variable is required")
exit(1)
if not csrf_token:
print("❌ LEETCODE_CSRF_TOKEN environment variable is required")
exit(1)
if not cf_clearance:
print("❌ LEETCODE_CF_CLEARANCE environment variable is required")
exit(1)
configuration = leetcode.Configuration()
configuration.api_key["x-csrftoken"] = csrf_token
configuration.api_key["csrftoken"] = csrf_token
configuration.api_key["LEETCODE_SESSION"] = leetcode_session_token
configuration.api_key["cf_clearance"] = cf_clearance
configuration.api_key["Referer"] = "https://leetcode.com"
configuration.debug = False
return leetcode.DefaultApi(leetcode.ApiClient(configuration))
def get_question_metadata(api, title_slug):
graphql_request = leetcode.GraphqlQuery(
query='''query questionData($titleSlug: String!) {
question(titleSlug: $titleSlug) {
questionId
title
difficulty
companyTagStatsV2
isPaidOnly
topicTags {
name
}
}
}
''',
variables=leetcode.GraphqlQueryGetQuestionDetailVariables(
title_slug=title_slug)
)
try:
response = api.graphql_post(body=graphql_request)
if not response.data.question:
print(f'❌ Empty response body for question: {title_slug}')
exit(1)
return response
except ApiException as e:
print(
f'Exception occurred when contacting the Leetcode GraphQL API: ${e}')
exit()
def construct_company_tag_list(company_tag_stats_v2):
companies = []
tag_stats = json.loads(company_tag_stats_v2)
# "three_months" = 0-3 months, "six_months" = 3-6 months, "more_than_six_months" = 6+ months
for tag in tag_stats["three_months"] + tag_stats["six_months"]:
companies.append({
"name": tag["name"],
"slug": tag["slug"],
"frequency": tag["timesEncountered"]
})
return sorted(companies, key=lambda d: d['frequency'], reverse=True)
def update_question_metadata(question, response):
print(f'''🔄 Updating question metadata for {question["title"]}''')
question_id = response.data.question.question_id
question_title = response.data.question.title
question_difficulty = response.data.question.difficulty
question_company_tag_stats_v2 = response.data.question.company_tag_stats_v2
question_is_premium = response.data.question.is_paid_only
question_topic_tags = response.data.question.topic_tags
companies = construct_company_tag_list(question_company_tag_stats_v2)
patterns = [tag.name for tag in question_topic_tags]
question["id"] = int(question_id)
question["title"] = question_title
question["difficulty"] = question_difficulty
question["pattern"] = patterns
question["companies"] = companies
question["premium"] = question_is_premium
def read_questions(file_name):
print(f"💾 Loading {file_name}")
try:
with open(file_name, "r") as file:
questions = json.load(file)
print(f"✅ Finished loading {file_name}")
return questions
except Exception as e:
print(
f"❌ Exception occurred when reading {file_name}: {e}")
exit()
def write_questions(file_name, questions):
print(f"💾 Updating {file_name}")
try:
with open(file_name, "w") as file:
questions["updated"] = str(datetime.now().isoformat())
json.dump(questions, file, indent=2)
print(f"✅ Finished updating {file_name}")
except Exception as e:
print(
f"❌ Exception occurred when writing {file_name}: {e}")
exit()
def main(file_name):
api = create_leetcode_api()
questions = read_questions(file_name)
for question in questions["data"]:
title_slug = question["slug"]
response = get_question_metadata(api, title_slug)
update_question_metadata(question, response)
write_questions(file_name, questions)
if __name__ == "__main__":
file_name = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "src", "data", "questions.json")
startTime = datetime.now()
main(file_name)
print(f"⏱️ Data updated in {datetime.now() - startTime} seconds")
================================================
FILE: eslint.config.mjs
================================================
import { defineConfig, globalIgnores } from "eslint/config";
import nextVitals from "eslint-config-next/core-web-vitals";
import nextTs from "eslint-config-next/typescript";
const eslintConfig = defineConfig([
...nextVitals,
...nextTs,
// Override default ignores of eslint-config-next.
globalIgnores([
// Default ignores of eslint-config-next:
".next/**",
"out/**",
"build/**",
"next-env.d.ts",
]),
]);
export default eslintConfig;
================================================
FILE: next.config.ts
================================================
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
reactStrictMode: false,
output: "export",
trailingSlash: false,
basePath: process.env.NEXT_PUBLIC_BASE_PATH ?? "",
images: { unoptimized: true },
};
export default nextConfig;
================================================
FILE: package.json
================================================
{
"name": "leetcode-patterns-v2",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build && node scripts/generate-sw-precache.mjs",
"start": "next start",
"lint": "eslint",
"test": "vitest run",
"test:watch": "vitest",
"prepare": "husky"
},
"dependencies": {
"@next/third-parties": "^16.1.6",
"@supabase/supabase-js": "^2.99.0",
"@tanstack/react-table": "^8.21.3",
"@tanstack/react-virtual": "^3.13.21",
"lucide-react": "^0.577.0",
"next": "16.1.7",
"react": "19.2.3",
"react-dom": "19.2.3"
},
"devDependencies": {
"@tailwindcss/postcss": "^4",
"@testing-library/jest-dom": "^6.9.1",
"@testing-library/react": "^16.3.2",
"@testing-library/user-event": "^14.6.1",
"@types/node": "^20",
"@types/react": "^19",
"@types/react-dom": "^19",
"@vitejs/plugin-react": "^5.1.4",
"@vitest/coverage-v8": "^4.0.18",
"eslint": "^9",
"eslint-config-next": "16.1.6",
"happy-dom": "^20.8.3",
"husky": "^9.1.7",
"jsdom": "^28.1.0",
"tailwindcss": "^4",
"typescript": "^5",
"vitest": "^4.0.18"
}
}
================================================
FILE: postcss.config.mjs
================================================
const config = {
plugins: {
"@tailwindcss/postcss": {},
},
};
export default config;
================================================
FILE: public/.nojekyll
================================================
================================================
FILE: public/manifest.json
================================================
{
"short_name": "leetcode-patterns",
"name": "Leetcode Patterns",
"description": "A curated list of LeetCode questions grouped by patterns.",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#09090b",
"background_color": "#ffffff"
}
================================================
FILE: public/robots.txt
================================================
User-agent: *
Allow: /
================================================
FILE: public/sw.js
================================================
// Service Worker — cache-first for static assets, network-first for navigation
const CACHE_NAME = "lc-patterns-v2";
const PRECACHE_URLS = [
"./",
"./manifest.json",
];
self.addEventListener("install", (event) => {
event.waitUntil(
caches.open(CACHE_NAME).then((cache) => cache.addAll(PRECACHE_URLS))
);
self.skipWaiting();
});
self.addEventListener("activate", (event) => {
event.waitUntil(
caches.keys().then((keys) =>
Promise.all(keys.filter((k) => k !== CACHE_NAME).map((k) => caches.delete(k)))
)
);
self.clients.claim();
});
self.addEventListener("fetch", (event) => {
const { request } = event;
// Skip non-GET and chrome-extension requests
if (request.method !== "GET" || !request.url.startsWith("http")) return;
// Skip analytics and external API calls
if (
request.url.includes("google-analytics.com") ||
request.url.includes("googletagmanager.com") ||
request.url.includes("s2/favicons")
) return;
// Navigation requests: network-first with cache fallback
if (request.mode === "navigate") {
event.respondWith(
fetch(request)
.then((response) => {
const clone = response.clone();
caches.open(CACHE_NAME).then((cache) => cache.put(request, clone));
return response;
})
.catch(() => caches.match(request).then((cached) => cached || caches.match("./")))
);
return;
}
// Static assets: cache-first with network fallback
event.respondWith(
caches.match(request).then(
(cached) =>
cached ||
fetch(request).then((response) => {
// Only cache successful same-origin responses
if (response.ok && request.url.startsWith(self.location.origin)) {
const clone = response.clone();
caches.open(CACHE_NAME).then((cache) => cache.put(request, clone));
}
return response;
})
)
);
});
================================================
FILE: scripts/generate-sw-precache.mjs
================================================
import { readFileSync, writeFileSync, readdirSync, statSync } from "fs";
import { join, relative } from "path";
const OUT_DIR = "out";
const SW_PATH = join(OUT_DIR, "sw.js");
const PRECACHE_EXTENSIONS = new Set([
".html", ".js", ".css", ".json", ".txt", ".woff", ".woff2",
]);
function collectFiles(dir, base = dir) {
const entries = [];
for (const entry of readdirSync(dir)) {
const full = join(dir, entry);
if (statSync(full).isDirectory()) {
entries.push(...collectFiles(full, base));
} else {
const rel = "./" + relative(base, full);
if (rel === "./sw.js") continue;
if (rel.endsWith(".map")) continue;
const ext = rel.slice(rel.lastIndexOf("."));
if (!PRECACHE_EXTENSIONS.has(ext)) continue;
entries.push(rel);
}
}
return entries;
}
const files = collectFiles(OUT_DIR);
const sw = readFileSync(SW_PATH, "utf-8");
// Replace the placeholder PRECACHE_URLS array with the full file list
const updated = sw.replace(
/const PRECACHE_URLS = \[[\s\S]*?\];/,
`const PRECACHE_URLS = ${JSON.stringify(files, null, 2)};`
);
writeFileSync(SW_PATH, updated);
console.log(`Precache manifest: ${files.length} files injected into sw.js`);
================================================
FILE: src/app/globals.css
================================================
@import "tailwindcss";
@custom-variant dark (&:where(.dark, .dark *));
:root {
--background: #ffffff;
--foreground: #171717;
}
.dark {
--background: #0a0a0a;
--foreground: #ededed;
}
@theme inline {
--color-background: var(--background);
--color-foreground: var(--foreground);
--font-sans: var(--font-geist-sans);
--font-mono: var(--font-geist-mono);
}
body {
background: var(--background);
color: var(--foreground);
font-family: Arial, Helvetica, sans-serif;
}
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(1rem);
}
to {
opacity: 1;
transform: translateY(0);
}
}
================================================
FILE: src/app/layout.tsx
================================================
import type { Metadata } from "next";
import { GoogleAnalytics } from "@next/third-parties/google";
import { Geist, Geist_Mono, Dancing_Script } from "next/font/google";
import "./globals.css";
import ServiceWorkerRegistrar from "@/components/layout/ServiceWorkerRegistrar";
import { AuthProvider } from "@/components/layout/AuthContext";
const geistSans = Geist({
variable: "--font-geist-sans",
subsets: ["latin"],
});
const geistMono = Geist_Mono({
variable: "--font-geist-mono",
subsets: ["latin"],
});
const dancingScript = Dancing_Script({
variable: "--font-dancing-script",
subsets: ["latin"],
});
const siteUrl = "https://seanprashad.com/leetcode-patterns";
const basePath = process.env.NEXT_PUBLIC_BASE_PATH ?? "";
export const metadata: Metadata = {
title: "Leetcode Patterns",
description:
"A curated list of LeetCode questions grouped by pattern to help you ace coding interviews. Filter by difficulty, company, and topic.",
manifest: `${basePath}/manifest.json`,
metadataBase: new URL(siteUrl),
alternates: { canonical: "/" },
openGraph: {
title: "Leetcode Patterns",
description:
"A curated list of LeetCode questions grouped by pattern to help you ace coding interviews.",
url: siteUrl,
siteName: "Leetcode Patterns",
images: [
{
url: `${basePath}/images/og-image.png`,
width: 1200,
height: 630,
alt: "Leetcode Patterns – A curated list of LeetCode questions grouped by pattern",
},
],
type: "website",
},
twitter: {
card: "summary_large_image",
title: "Leetcode Patterns",
description:
"A curated list of LeetCode questions grouped by pattern to help you ace coding interviews.",
images: [`${basePath}/images/og-image.png`],
},
keywords: [
"leetcode",
"coding interview",
"data structures",
"algorithms",
"interview prep",
"blind 75",
"leetcode patterns",
],
authors: [{ name: "Sean Prashad", url: "https://github.com/SeanPrashad" }],
};
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (