The Myth of the One-Size-Fits-All Resume: Why Customization Matters

Most job seekers have been told some version of the same advice: create one strong resume, polish it, and send it everywhere. It sounds efficient. It feels practical. And when job hunting is already stressful, the idea of reworking your resume for every role can seem unrealistic.

But that advice is one of the biggest myths in the hiring process.

A resume is not just a summary of your past. It is a targeted document designed to show why you fit a specific opportunity. When the same resume is used for every application, it often misses what recruiters, hiring managers, and applicant tracking systems are actually looking for. The result is frustratingly familiar: lots of applications, very few interviews.

For job seekers, this matters because customization can improve visibility and relevance. For recruiters and HR teams, it matters because a tailored resume makes it easier to identify qualified candidates quickly. In other words, customization is not about gaming the system. It is about making the match clearer for everyone involved.

In this article, we will break down why the one-size-fits-all resume usually underperforms, what real customization looks like, how ATS and recruiters evaluate relevance, and how to tailor your resume without spending hours on every application.

Why the one-size-fits-all resume became so common

The generic resume did not become popular because it works best. It became popular because it feels manageable.

Most job seekers are dealing with real constraints:

  • They need to apply quickly
  • They may be applying to dozens of roles
  • They are often balancing current work, family, or financial pressure
  • They have been told that volume is the key to success

That leads to a simple strategy: build one master resume and send it everywhere.

On the surface, this approach offers a few short-term benefits:

  • It saves time upfront
  • It reduces decision fatigue
  • It creates a sense of momentum
  • It avoids the discomfort of rewriting

The problem is that efficiency at the application stage often creates inefficiency later. If your resume does not align with the role, your application may never get serious consideration. That means more applications are needed to produce the same result, and sometimes even that is not enough.

A resume that tries to speak to every role usually ends up speaking clearly to none of them.

What recruiters and hiring managers actually look for

Recruiters and hiring managers are not reading resumes like biographies. They are scanning for evidence of fit.

In most cases, they are trying to answer a small set of questions very quickly:

  • Does this person match the core requirements of the role?
  • Have they done similar work before?
  • Do their skills align with the team’s needs?
  • Can I easily connect their background to this job?

If those answers are not obvious within seconds, many resumes lose momentum immediately.

That is why customization matters so much. It helps reduce the amount of interpretation required. Instead of asking the recruiter to connect the dots, a tailored resume connects them directly.

A recruiter is not searching for everything you have done

This is where many job seekers go wrong. They assume that a broader resume is a stronger resume because it shows more capability.

In reality, too much unrelated information can weaken your message.

Imagine a candidate applying for a product marketing role. Their resume includes:

  • customer support metrics
    n- event planning experience
  • social media content creation
  • sales support tasks
  • basic design work
  • product launch collaboration
  • internal training documentation

All of that may be true. But if the target role emphasizes go-to-market strategy, messaging, campaign analysis, and cross-functional launch work, then the most important parts of that background need to be prioritized. Otherwise, the resume reads like a list of tasks instead of a case for fit.

Customization is not about hiding experience. It is about organizing experience around relevance.

Why ATS optimization and customization go together

Applicant tracking systems are often discussed in ways that create confusion. Some people treat ATS like a robot gatekeeper that can be tricked with keyword stuffing. Others dismiss it entirely.

The truth is more practical.

An ATS is usually there to help employers store, sort, search, and manage applications. Different systems work differently, but the common theme is that your resume needs to be readable, structured, and aligned with the language of the role.

That means customization helps in at least three ways:

1. It improves keyword alignment

Job descriptions contain signals about what the employer values. If a role asks for:

  • stakeholder management
  • project coordination
  • vendor communication
  • reporting and documentation

and your resume uses only broad phrases like “supported business operations,” you may be underselling your fit.

The issue is not just keywords for software. It is also clarity for human readers.

2. It increases context around your experience

Many candidates have relevant experience but describe it too vaguely. Tailoring your bullet points helps you show not just what you did, but why it matters for this specific job.

3. It makes your resume easier to rank and review

If recruiters search their system using certain terms, a tailored resume is more likely to appear in the right context. If it does appear, the match is easier to see.

If you want a faster way to create resumes that are structured for ATS readability, tools like GetQuickResume can help simplify the process while keeping the final document professional and easy to adapt.

The real cost of using the same resume everywhere

A generic resume does not always fail because it is bad. Often, it fails because it is too broad to be persuasive.

Here are the most common costs.

1. You look less qualified than you are

This is one of the most frustrating outcomes. A candidate may be fully capable of doing the job, but their resume does not make the match visible enough.

That creates a false negative: the person is qualified, but the resume does not communicate it clearly.

2. Your strongest experience gets buried

When every role, project, and skill receives equal weight, your most relevant achievements lose impact. Important information becomes harder to find.

3. You blend in with other applicants

Recruiters see many resumes that look interchangeable. Generic summaries, generic skill lists, and generic responsibilities create a sameness problem.

Customization helps you stand out not by sounding louder, but by sounding more precise.

4. You waste time through low response rates

This seems counterintuitive at first. Tailoring sounds slower. But if a tailored resume increases relevance and interview chances, the total time invested in your job search may actually go down.

Sending 100 generic applications is not always more efficient than sending 25 strong, targeted ones.

What customization actually means

Many people hear “customize your resume” and assume it means rewriting the whole document from scratch every time.

That is not necessary.

Good customization usually means adjusting a core resume in a focused and strategic way.

The parts that often change

For most applications, the following sections deserve review:

Headline or professional summary

This should reflect the type of role you are targeting. A resume for a customer success manager role should not open with a summary centered on general administrative support.

Skills section

Prioritize the skills most relevant to the job description. Keep it honest, but do not make recruiters hunt for the right signals.

Experience bullet points

You do not need to rewrite every bullet. You do need to lead with the most relevant achievements and responsibilities.

Keywords and terminology

If the employer uses “client relationship management” and your resume says “customer communication,” there may be an opportunity to align language more clearly, as long as it remains accurate.

Selected achievements

Different roles call for different proof points. A sales operations role may value reporting, pipeline visibility, and process improvements. A customer success role may care more about retention, onboarding, and account growth.

The parts that usually stay consistent

These do not usually need major changes:

  • Employment dates
    n- Core job history
  • Education
  • Certifications
  • Honest baseline qualifications

The goal is not reinvention. It is emphasis.

A practical framework for resume customization

Here is a simple system that job seekers can use without making the process overwhelming.

Step 1: Create a master resume

Your master resume is not what you send out. It is your source file.

Include:

  • all relevant roles
  • more bullet points than you would normally use
  • key achievements
  • measurable outcomes where possible
  • tools, certifications, and training
  • multiple versions of your summary if useful

Think of it as your complete inventory.

Step 2: Analyze the job description

Before editing your resume, read the job posting with intent.

Look for:

  • repeated skills or themes
  • required versus preferred qualifications
  • responsibilities mentioned early in the posting
  • business goals implied by the role
  • language that appears multiple times

Ask yourself:

  • What problem is this employer trying to solve?
  • What kind of candidate would make this role feel low-risk to hire?
  • Which parts of my background best answer that need?

Step 3: Match your experience to the role

Now compare your master resume to the posting.

Highlight:

  • directly relevant experience
  • transferable experience that supports the role
  • achievements that map to the employer’s priorities
  • terminology that can be aligned accurately

This is where many strong resumes are made. Not through dramatic rewriting, but through sharper matching.

Step 4: Reorder for relevance

You do not always need new content. Often, you need a better sequence.

Put the most relevant information where it will be seen first:

  • summary
  • top skills
  • first few bullets under each role
  • high-impact achievements

Recruiters often make judgments quickly. Order matters.

Step 5: Tighten the language

Replace vague statements with specific, role-aligned language.

For example:

Instead of:

  • Responsible for helping different teams and supporting day-to-day business tasks

Try:

  • Coordinated cross-functional project updates, maintained reporting dashboards, and improved documentation flow across operations and client-facing teams

The second version gives clearer signals about scope and relevance.

Step 6: Check readability and structure

Customization works best when the resume is also easy to scan.

Make sure it is:

  • cleanly formatted
  • free of dense paragraphs
  • consistent in tense and style
  • built around outcomes, not just duties
  • easy for both ATS and human readers to process

For people who want to save time on formatting and adaptation, GetQuickResume is one example of a resume builder that helps users create ATS-friendly resumes quickly, which can make customization much less time-consuming.

Tailoring by job type: examples that make the difference clear

Customization becomes easier when you see it in context.

Example 1: Project coordinator vs operations coordinator

At a glance, these roles may look similar. Both may involve timelines, communication, documentation, and process support.

But the emphasis often differs.

If the target role is project coordinator

Your resume should prioritize:

  • timeline tracking
  • stakeholder communication
  • status reporting
  • milestone management
  • cross-functional coordination

A stronger bullet might be:

  • Supported project delivery across three internal teams by tracking deadlines, updating status reports, and coordinating weekly stakeholder communications

If the target role is operations coordinator

Your resume should prioritize:

  • workflow improvement
  • reporting accuracy
  • process documentation
  • operational support
  • vendor or internal system coordination

A stronger bullet might be:

  • Improved internal operations by maintaining process documentation, streamlining recurring reporting, and coordinating daily administrative workflows across departments

Same background, different framing.

Example 2: Customer support vs customer success

Many candidates have experience that overlaps both areas. But the hiring goals are different.

Customer support resumes often emphasize

  • ticket resolution
  • response time
  • issue escalation
  • product troubleshooting
  • service quality

Customer success resumes often emphasize

  • onboarding
  • retention
  • relationship management
  • account health
  • proactive communication

A generic resume may mention all of these equally. A tailored resume makes the target function obvious.

Example 3: Software-adjacent roles on dev.to

Because this article is for dev.to, it is worth noting that many readers work in or around technical teams without being full-time developers. Roles like technical recruiter, product operations specialist, implementation manager, QA analyst, solutions consultant, and developer relations coordinator often require resumes that translate cross-functional experience into the specific language of the role.

For example, a solutions consultant applying to a more technical client-facing role may want to elevate:

  • implementation support
  • API or integration exposure
  • discovery calls
  • technical documentation
  • stakeholder alignment

While the same candidate applying for account management may instead emphasize:

  • client retention
  • upsell support
  • onboarding satisfaction
  • relationship-building
  • business reviews

The underlying work may overlap, but the employer is hiring for a particular lens.

The difference between customization and keyword stuffing

This is an important distinction.

Customization is strategic alignment.
Keyword stuffing is forced repetition.

A good tailored resume:

  • uses language naturally
  • reflects real experience
  • mirrors the role where accurate
  • stays readable and credible

A poorly optimized resume:

  • repeats phrases unnaturally
  • lists skills without evidence
  • copies entire sections from the job description
  • sounds generic despite having many keywords

Recruiters can usually tell the difference quickly.

A useful rule is this: if you cannot support a keyword with real work, results, or context, do not force it in.

Common resume customization mistakes

Even when job seekers try to tailor their resumes, a few mistakes show up repeatedly.

1. Changing only the job title at the top

This is not enough. If the summary says one thing but the bullets underneath tell another story, the resume still feels generic.

2. Ignoring the employer’s language

If the posting emphasizes account retention, lifecycle management, and onboarding, but your resume sticks to broad language like “worked with clients,” you may be missing easy relevance.

3. Over-editing until the resume loses truth

Customization should sharpen reality, not distort it. Do not reshape your past so aggressively that the resume becomes misleading.

4. Leaving transferable skills too vague

Candidates making a career pivot often do have relevant strengths, but they present them too indirectly. Transferable skills need concrete examples.

Instead of saying:

  • Strong communication and leadership skills

show it through experience:

  • Led weekly client update calls, documented next steps, and coordinated follow-up across internal teams to keep projects on schedule

5. Using the same summary for every application

The summary is valuable because it frames the entire resume. If it is too broad, it weakens everything below it.

How recruiters evaluate tailored resumes

From a recruiter or HR perspective, a tailored resume is useful because it reduces friction.

It helps answer three practical concerns:

Can this candidate do the job?

Relevant experience is easier to spot when the resume reflects the position directly.

Will this candidate understand the environment?

Role-specific language and examples suggest familiarity with the type of work, pace, and expectations involved.

Is this candidate serious about this opportunity?

A tailored resume often signals intent. It shows the applicant has thought about the role instead of mass applying with the same document.

This does not guarantee an interview. But it often improves first impressions because it makes evaluation easier.

Authority check: what we can say with confidence

There is a lot of bad advice in the resume space, especially advice built on dramatic claims or invented numbers. So it is worth being clear about what is reasonable to say.

We do not need fake statistics to understand why customization works.

We know that:

  • employers write job descriptions to define priorities
  • recruiters scan resumes for relevance, not completeness
  • ATS platforms rely on structured, searchable application data
  • clearer alignment reduces ambiguity in screening
  • strong communication improves decision-making in hiring, just like in any other business process

In other words, the case for customization is grounded in logic and workflow, not hype.

A tailored resume performs better because it is easier to understand in the context of a specific hiring need.

That is not a trick. It is good communication.

How to customize faster without burning out

The biggest objection to customization is time.

That objection is valid.

If resume tailoring feels like rewriting your professional story from zero every time, it will be hard to sustain. The solution is to build a repeatable system.

Use a modular resume approach

Create reusable versions of key sections:

  • 2 to 4 summary statements for different role families
  • a larger bank of bullet points for each job
  • a list of measurable achievements
  • multiple skill clusters depending on role type

Then each application becomes a selective editing process, not a blank page exercise.

Group jobs by target category

If you are applying to similar roles, build category-specific versions.

For example:

  • operations resume
  • project coordination resume
  • customer success resume
  • technical support resume

This gives you a strong starting point for each role family.

Track what version you sent

Keep a simple spreadsheet or note with:

  • company name
  • role title
  • date applied
  • version used
  • key edits made

This helps with follow-ups and interviews, especially if you are applying to many positions.

Focus on the top third of the resume first

If time is limited, prioritize the areas most likely to influence first impressions:

  • summary
  • skills
  • first few bullets in recent roles

Even partial customization is usually better than none.

Keep your process honest and lightweight

Do not try to reinvent yourself for every role. Try to present your strongest relevant truth more clearly.

That mindset makes customization faster and more sustainable.

A simple before-you-apply checklist

Before sending a resume, ask:

  • Does my summary reflect this type of role?
  • Are the most relevant skills easy to find?
  • Do my top bullet points connect clearly to the job description?
  • Have I used role-relevant language where accurate?
  • Is the resume clean, readable, and consistent?
  • Does this version make the fit obvious within a short scan?

If the answer to most of these is yes, you are in a much better position than candidates who send the same resume unchanged.

What job seekers, recruiters, and HR teams can all agree on

Despite different goals, all three groups benefit from better resume customization.

Job seekers benefit because

  • their experience is framed more clearly
  • their qualifications are easier to recognize
  • they can improve interview chances without exaggeration
  • they spend less time lost in low-response application cycles

Recruiters benefit because

  • screening becomes faster
  • relevant applicants are easier to identify
  • candidate comparisons become clearer
  • fewer good-fit candidates are overlooked because of vague presentation

HR teams benefit because

  • hiring workflows become more efficient
  • role alignment improves earlier in the funnel
  • communication gaps are reduced before interviews begin

That shared benefit is why customization matters so much. It improves signal quality for everyone.

Actionable takeaways

If you want to move from generic resume sending to a more effective system, start here:

  1. Build one strong master resume with all your experience and achievements.
  2. Identify 2 to 4 target role categories instead of applying blindly to everything.
  3. Create a tailored summary for each category.
  4. Reorder bullet points so the most relevant information appears first.
  5. Match language from the job description where it reflects your real experience.
  6. Keep your formatting simple and ATS-friendly.
  7. Track which version you use for each application.
  8. Review response rates and refine your approach over time.

And if your current resume process feels too slow or messy, using a tool like GetQuickResume can make it easier to build a clean, professional base resume that you can customize quickly for different roles without starting from scratch each time.

Conclusion

The one-size-fits-all resume is appealing because it promises speed. But in practice, it often creates the opposite outcome: more applications, more silence, and more frustration.

Customization works because hiring is contextual. Employers are not hiring a general history. They are hiring for a specific need. The closer your resume gets to that need, the easier it becomes for recruiters and hiring managers to recognize your value.

That does not mean rewriting your life story for every application. It means being intentional about emphasis, language, and relevance.

A better resume is not always longer, flashier, or more complicated. Often, it is simply clearer.

If there is one takeaway worth remembering, it is this: your resume should not try to say everything. It should make the right things obvious.

And when you build your process around that idea, customization stops feeling like extra work and starts feeling like one of the smartest ways to save time and improve results in your job search.

How to Add Address Autocomplete to a Python App – Free API Tutorial

Your checkout form collects addresses as freeform text. Users mistype street names, skip apartment numbers, and guess at zip codes. That bad data flows into your database, and each failed delivery costs $15-20 to re-ship.

Address autocomplete fixes the problem at the source. Users type a few characters, pick the correct address from a dropdown, and you get a postal-formatted string with unit numbers and zip+4 – ready to print on a shipping label.

This tutorial shows you how to add US address autocomplete to a Python app using sthan.io‘s address API. Works with Flask, Django, FastAPI, or any Python backend.

Quick summary: Install requests, get free credentials from sthan.io, call GET /AutoComplete/USA/Address/{text} with a Bearer token. You get back a JSON array of formatted US addresses – no credit card required.

What you’ll need: Python 3.7+ and a free sthan.io account. No credit card, no approval process. The free tier gives you 100,000 requests/month – enough for roughly 20,000 address lookups (assuming ~5 keystrokes per lookup).

Try it first

Type any partial US address – no signup required:

That’s what you’re building. Type “123 main st” – lowercase, abbreviated, no city or state – and the API returns complete, postal-formatted addresses with apartment numbers, zip+4 codes, and proper casing.

What the API returns

The API wraps every response in an envelope. The address suggestions are in the Result field:

{
  "Id": "3f2504e0-4f89-11d3-9a0c-0305e82c3301",
  "Result": [
    "123 Main St APT 1, Andover, MA 01810-3816",
    "123 Main St APT 1, Delhi, NY 13753-1257",
    "123 Main St STE 1, Caldwell, ID 83605-5476",
    "123 Main St STE 1, Corinth, NY 12822-1010",
    "123 Main St STE 1, Delhi, NY 13753-1258"
  ],
  "StatusCode": 200,
  "IsError": false,
  "Errors": []
}

Each address in Result includes the full street, unit designation (APT, STE, UNIT), city, state code, and zip+4. The API handles abbreviations (St, Ave, Blvd) and directional prefixes (N, S, E, W).

Step 1: Get your API credentials

  1. Sign up at sthan.io
  2. Go to the API Keys page
  3. Copy your Profile Name and Profile Password

You get credentials immediately – no approval queue.

Step 2: Install requests

pip install requests

Step 3: Get an auth token

The API uses JWT authentication. First call gets a token, then you use it for autocomplete requests.

import os
import requests

PROFILE_NAME = os.environ.get("STHAN_PROFILE_NAME", "YOUR_PROFILE_NAME")
PROFILE_PASSWORD = os.environ.get("STHAN_PROFILE_PASSWORD", "YOUR_PROFILE_PASSWORD")
BASE_URL = "https://api.sthan.io"

def get_token():
    response = requests.get(
        f"{BASE_URL}/Auth/Token",
        headers={
            "profileName": PROFILE_NAME,
            "profilePassword": PROFILE_PASSWORD,
        },
    )
    response.raise_for_status()
    data = response.json()
    result = data["Result"]
    return result["access_token"], result["expiration"]

token, expiration = get_token()
print(f"Token expires: {expiration}")

The token lasts 15 minutes. The production client below caches and refreshes it automatically.

Step 4: Call the autocomplete endpoint

from urllib.parse import quote

def autocomplete(query, token):
    response = requests.get(
        f"{BASE_URL}/AutoComplete/USA/Address/{quote(query)}",
        headers={"Authorization": f"Bearer {token}"},
    )
    response.raise_for_status()
    return response.json()["Result"]

results = autocomplete("123 main st", token)
for address in results:
    print(address)

The output matches the JSON shown above – the same five addresses, standardized from the abbreviated input.

Step 5: Production-ready client with token caching

In production, you don’t want to fetch a new token on every request. Here’s a client class that caches the token and refreshes it on expiry:

import os
import requests
from urllib.parse import quote
from datetime import datetime, timezone


class SthanClient:
    def __init__(self, profile_name=None, profile_password=None):
        self.profile_name = profile_name or os.environ["STHAN_PROFILE_NAME"]
        self.profile_password = profile_password or os.environ["STHAN_PROFILE_PASSWORD"]
        self.base_url = "https://api.sthan.io"
        self._token = None
        self._expiration = None

    def _get_token(self):
        if self._token and self._expiration:
            if datetime.now(timezone.utc) < self._expiration:
                return self._token

        response = requests.get(
            f"{self.base_url}/Auth/Token",
            headers={
                "profileName": self.profile_name,
                "profilePassword": self.profile_password,
            },
        )
        response.raise_for_status()
        data = response.json()["Result"]
        self._token = data["access_token"]
        self._expiration = datetime.fromisoformat(
            data["expiration"].replace("Z", "+00:00")
        )
        return self._token

    def autocomplete(self, query, min_length=3):
        """Fetch address suggestions for a partial query."""
        if len(query.strip()) < min_length:
            return []

        token = self._get_token()
        response = requests.get(
            f"{self.base_url}/AutoComplete/USA/Address/{quote(query)}",
            headers={"Authorization": f"Bearer {token}"},
        )
        response.raise_for_status()
        return response.json()["Result"]


# Usage
client = SthanClient()

results = client.autocomplete("123 main st")
for address in results:
    print(address)

This client:

  • Caches the JWT and reuses it across requests
  • Refreshes automatically when the token expires
  • Enforces a minimum query length (3 characters) to avoid wasting API calls
  • Reads credentials from environment variables by default
  • Works with any Python web framework

Step 6: Add it to a Flask app

Here’s a minimal Flask endpoint that your frontend can call:

from flask import Flask, request, jsonify

app = Flask(__name__)
client = SthanClient()  # reads from STHAN_PROFILE_NAME / STHAN_PROFILE_PASSWORD env vars


@app.route("/api/autocomplete")
def autocomplete_endpoint():
    query = request.args.get("q", "")
    if len(query) < 3:
        return jsonify([])
    results = client.autocomplete(query)
    return jsonify(results)


if __name__ == "__main__":
    app.run(port=3000)

Your frontend calls /api/autocomplete?q=123 main st and gets back a JSON array of addresses. Credentials stay on the server – never exposed to the browser.

The same pattern works with Django (views.py), FastAPI (@app.get), or any other Python web framework.

Step 7: Handle errors

Two error codes to handle:

  • 401 – Token expired. Refresh and retry.
  • 429 – Rate limit hit. Back off and show the user what they’ve typed so far.
def autocomplete_with_retry(client, query):
    try:
        return client.autocomplete(query)
    except requests.HTTPError as e:
        if e.response.status_code == 401:
            # Force token refresh and retry once
            client._token = None
            return client.autocomplete(query)
        elif e.response.status_code == 429:
            # Rate limited - return empty, don't crash
            return []
        raise

What’s next

Address autocomplete is step 1 of a complete address pipeline. The same credentials unlock these endpoints:

For a complete Python guide covering all endpoints with async examples, see Integrate Address APIs in Python.

FAQ

How do I add address autocomplete to a Python Flask app?
Install requests, create a SthanClient with your free sthan.io credentials, and expose a /api/autocomplete endpoint. The full working Flask example is in Step 6 above. The same approach works with Django and FastAPI.

How much does the address autocomplete API cost?
The free tier gives you 100,000 requests/month – roughly 20,000 address lookups (assuming ~5 keystrokes per lookup). No credit card required. No trial period. See pricing for higher-volume plans.

What makes this different from geocoding-based autocomplete?
Geocoding APIs are designed to find places – restaurants, landmarks, businesses. They often drop apartment numbers, ignore suite designations, and don’t return zip+4 codes. A dedicated address autocomplete API returns postal-formatted mailing addresses from the first keystroke: unit numbers, zip+4, and standard postal abbreviations included.

Does the API support CORS for browser requests?
No. Call it from your Python backend, not the browser. The Flask proxy in Step 6 handles this – your frontend calls your server, your server calls the API.

What is the response time for autocomplete requests?
Typically under 100ms. The API is designed for real-time suggestions as users type.

Can I use this with Django or FastAPI instead of Flask?
Yes. The SthanClient class is framework-agnostic. Wire it to any route that accepts a query parameter. For Django: use it in views.py. For FastAPI: use it with @app.get.

Get started

The code above works. Copy it. The free tier covers 20,000 address lookups a month with no credit card.

Create your free sthan.io account and add autocomplete to your Python app.

sthan.io – Address APIs Built for Scale, Priced for Everyone

Closed-world assumption in Java

1357_cwa_in_java/image1.png

Building Native Image for a Java application requires configuration of reflection, proxies, and other dynamic Java mechanisms. But why is this necessary if the JVM handles all of this automatically? To answer that, we need to look at the differences between static and dynamic compilation in Java.

1357_cwa_in_java/image1.png

Issue

At PVS-Studio, we are currently designing new static analyzers for JavaScript/TypeScript and Go to complement our existing tools. The Java team was tasked with developing the first version of the JavaScript/TypeScript analyzer. To avoid distributing a JRE and to gain performance profit, we decided to build the JavaScript/TypeScript analyzer into Native Image, i.e., to turn the Java application into a native program.

However, everything had its price, even performance. GraalVM immediately laid out its requirements: we had to explicitly specify in the configuration which classes would be accessed via reflection, which proxies would be created, which resources would be included in the binary, and which functions would be called via the Foreign Function & Memory API.

This raised a natural question: “Why does the VM automatically handle all dynamic behavior, while native build require configuration files?”

The intuitive response: “That’s just how GraalVM works.” But that explanation feels unsatisfying at all. It gave the impression that Native Image might be an immature technology or a questionable compromise. Of course, that’s not the case. To understand why, we need to step back from Java for a moment and look at a more fundamental question: what does it actually mean to execute a program?

Recap: What is a program, and how is it executed?

When we write code in Java, Kotlin, or another object-oriented language, we work with classes, methods, and packages. However, from the processor’s perspective, none of this exists. All code ultimately gets converted into binary data: sets of instructions, constants, and data.

To pass control to the section or access data, the processor needs the exact address. These addresses can be obtained in several ways:

  • fixed (hard-coded);
  • relative (calculated based on the current position);
  • dynamic (using tables, pointers, and late binding).

In statically compiled languages such as C, C++, Go, or Rust, address resolution happens during compilation via the compiler and linker.

A linker is a program that combines object files (output by the compiler) into a single executable file or library. It matches method calls with their definitions.

During the linking, all symbols and invocation points are known, so the linker has no trouble building the binary.

A symbol is a named entity in the code (a function and a variable).

Dynamic Java

Unlike statically compiled languages, where all addresses are resolved by the time the program runs and no additional loading occurs, Java was originally designed as a dynamic runtime environment. Its model is fundamentally different: the program is not compiled into a finished binary with fixed addresses, but remains a set of bytecode that is interpreted and optimized at runtime.

Several fundamental language properties help the virtual machine implement this behavior:

  • classes are not loaded entirely during the startup, but as needed via ClassLoader;
  • in bytecode, method calls are represented by symbolic references rather than addresses;
  • the actual method that will be called is determined only at runtime;
  • class metadata (fields, methods, annotations) is preserved and accessible at runtime.

A symbolic reference refers to a class instance using its name rather than its direct memory address. For example, it can call PrintWriter#println instead of going to the 0x954A address.

This allows Java to have such goodies as reflection, dynamic proxies, ServiceLoader, and DI frameworks like Spring, Micronaut, or Quarkus.

At the core of this dynamism is an architecture in which the virtual machine serves as the center of the Java “universe.” The program “revolves” around the JVM: it retrieves addresses from it, calls methods, and entrusts the entire execution process to it.

Essentially, the JVM serves as the central hub of execution:

  • all classes revolve around it;
  • all calls go through it;
  • all decisions about what exactly will be called are made by it at runtime.

In other words, a Java application does not contain fixed addresses—the JVM determines them.

Next, I’ll try to explain how the JVM works on the example of a BMW axle.

The JVM is like the BMW X6 axle

Imagine the JVM not as a virtual machine, but as the axle of a BMW X6 powered by a 4.4-liter S63B44T4 V8 engine producing 625 horsepower.

The whole system can look impressive: the engine delivers hundreds of horsepower, the electronics are sophisticated, and it has a multi-link rear suspension. But it is the axle that connects rotation, load, and motion into a single, coherent system.

The JVM plays that very role. It’s the structural element through which all aspects of program execution are coordinated and brought together.

To be more specific, the JVM:

  • controls class loading;
  • allows symbolic references;
  • stores and interprets metadata;
  • determines which method implementation to invoke;
  • supports reflection, proxies, and dynamic behavior substitution.

Let’s get even more specific. The Java program says, “I want to call a method named equals with the (Object)Object signature.” The JVM responds: “Okay, let’s find its address and call it.”

As long as this “axle” exists, everything works as it should. But Native Image removes it, leaving nothing to coordinate the components with.

Leap of faith: Native Java build

A native build attempts to turn a program designed to run within a complex dynamic environment into a self-contained binary that runs directly on the operating system.

This means that:

  • the JVM, as the central hub, disappears;
  • decisions previously made at runtime must now be made ahead of time.

The linker requires a complete and closed call graph, and this is where a fundamental contradiction arises.

In Java:

  • classes can be loaded dynamically;
  • methods can be called via reflection;
  • proxies can be generated at runtime.

A static linker views it as a black box. In general, it is impossible to determine in advance exactly what will be called. What should we do?

Why not just enable everything?

At first glance, the solution seems obvious: if we can’t determine what will be used, enable everything. In practice, however, this approach does not work.

First, it’s binary size. Including all classes, metadata, and JVM infrastructure means increasing the binary size by several times. One of the key advantages of Native Image is lost.

Second, it’s compilation performance. Analyzing and parsing the data requires significantly more time and memory.

But even if we’re ready to sacrifice size and build time, we’d still face a third problem: reflection wouldn’t work automatically anyway. What matters is not just the existence of classes, but the preservation of specific metadata: constructors, methods, and signatures. Without knowing in advance what will be accessed via reflection, it’s impossible to retain all required metadata correctly.

So, the include-everything strategy fails both in theory and in practice.

Graal Native Image Configuration

Native Image is based on the closed-world assumption: all possible execution paths must be known at compile time. The build process relies on reachability analysis:

  • a call graph is constructed;
  • everything not included in this graph is considered unreachable and removed.

Java’s dynamic mechanisms break this rule: reflection, proxies, and resources introduce hidden entry points—code paths that may be invoked at runtime but are invisible during analysis. Even code that seems static can generate such implicit calls, which means it requires configuration for native compilation.

We encountered this issue when we needed to read a value from the Windows Registry within the analyzer. We couldn’t find a suitable library, so we decided to use the relatively new Foreign Function & Memory API, which allows native methods to be called directly from Java.

The actual registry access code would be interesting but too complex for an example, so let’s simplify it to the standard “Hello, World” output in the console.

class Sandbox {
  void main() throws Throwable {
    Linker linker = Linker.nativeLinker(); // (1)
    MethodHandle printf = linker.downcallHandle( // (2)
        linker.defaultLookup().findOrThrow("printf"), // (3)
        FunctionDescriptor.of(ValueLayout.JAVA_INT, ValueLayout.ADDRESS)
    );

    try (Arena arena = Arena.ofConfined()) { // (4)
      MemorySegment cString = arena.allocateFrom("Hello, world!n"); // (5)
      printf.invoke(cString); // (6)
    }
  }
}

Here, we obtain a linker for native calls (1); create a MethodHandle for the printf method (2); resolve the method name at runtime (3); open an Arena, a scope for managing off-heap memory (4); allocate a block within it, write a C-style string into it (5), and call the method via the MethodHandle (6).

MethodHandle is a reference to executable code. You can learn more about this technology here.

FYI, the equivalent code in C looks like this:

int main() {
    printf("Hello, world!n");
    return 0;
}

On the JVM, this code works correctly, but when the Native Image runs with no appropriate configuration, an error occurs. The stack trace shows that the Native Image can’t set up the mechanism for calling the native method: it doesn’t know how to pass arguments or receive the result because the call signature wasn’t known at compile time. Graal is asking us to add information about the call point:

{
  "foreign": {
    "downcalls": [
      {
        "returnType": "jint", // (1)
        "parameterTypes": [ "void*" ] // (2)
      }
    ]
  }
}

This configuration doesn’t describe a specific method, but rather the form of a native call that may occur at runtime. From this, Graal learns the size of the return value, how it should be received and interpreted after control is returned (1), as well as the argument size, how it’s passed to native code, and which passing rules should be followed (2).

In other words, the configuration replaces decisions that the JVM would normally make at runtime.

Results

What can we take away from this?

The JVM can handle everything on its own because it is the runtime decision-making center. Native Image can’t rely on such improvisation because it makes all decisions ahead of time.

Configuration in GraalVM Native Image is not a quirk, a limitation, or a design flaw. It is a direct consequence of Java’s dynamic nature and the attempt to bring it into the world of static compilation.

If the JVM is the axle around which a Java application revolves, the Native Image is a snapshot of that system frozen at a specific moment. And to ensure that this snapshot is correct, we need to explicitly specify in the configuration exactly what should be included.

Two Pointers Pattern in DSA — Complete Guide for Beginners

This is a pattern guide. After reading this, you will be able to recognise and solve Two Pointers problems confidently — not just memorise one solution.

Let me ask you something.

Have you ever solved a LeetCode problem by running two loops — one inside the other — and it worked fine on small inputs but got a ‘Time Limit Exceeded’ on larger ones?

That’s almost always a sign that the problem has a Two Pointers solution waiting to be discovered.

Two Pointers is one of the most important patterns in DSA. Once you understand it deeply, you’ll start seeing it everywhere — in array problems, string problems, linked list problems, and even some graph problems.

This guide explains everything from scratch. No prior pattern knowledge needed.

What is the Two Pointers Pattern?
The idea is beautifully simple.

Instead of using one index to loop through an array, you use two — and move them strategically based on some condition. This allows you to check pairs, ranges, or partitions in a single pass instead of nested loops.

Here’s the key insight:

💡 A brute force solution that uses two nested loops is O(n²). Two Pointers converts many of those problems to O(n) — a massive improvement.
Think of it like this: imagine you have a sorted array of numbers and you want to find two numbers that add up to a target. The brute force approach checks every pair — that’s n × n combinations. The Two Pointers approach starts one pointer at the left end and one at the right end and works inward — that’s just n steps.

Same result. Dramatically less work.

When Should You Use Two Pointers?
The pattern fits when you spot these signals in a problem:

The input is a sorted array or string (or can be sorted without losing the answer)
You need to find a pair, triplet, or subarray satisfying some condition
You need to compare elements from both ends
You need to remove duplicates or partition elements in-place
The problem asks to minimise or maximise a window between two positions
⚠️ Not every array problem is a Two Pointers problem. If the array is unsorted and sorting changes the answer, think of a different approach.

Read Full Article: https://dailydevnotes.in/two-pointers-pattern-dsa/

Basic Programs in Python,Java & JavaScript: Smallest Number, Prime Number, and GCD

Cover

Finding the Smallest Number Among Three Using Ternary Operator

What is a Ternary Operator?

A ternary operator is a shorthand way of writing an if-else condition in a single line.

Syntax:

condition ? value_if_true : value_if_false;

JavaScript Code

let a = 5, b = 2, c = 8;

let smallest = (a<b && a<c) ? a : ((b<c) ? b : c)
console.log("Smallest number is: ")

Python Code

a = int(input("Enter number 1:"))
b = int(input("Enter number 2:"))
c = int(input("Enter number 3:"))

smallest = a if (a < b and a < c) else (b if b < c else c)
print("Smallest number is:", smallest)

Java Code

public class SmallestNumber {
    public static void main(String[] args) {

        int a = 5, b = 2, c = 8;

        int smallest = (a < b && a < c) ? a : ((b < c) ? b : c);

        System.out.println("Smallest number is: " + smallest);
    }
}

Output

How to find prime numbers within a given range

A prime number is a number greater than 1 that has only two factors:
1 and itself

Examples:
2, 3, 5, 7, 11…

Python Code:

no = 10
end =  50

while no <= end :
    div = 2
    flag = True

    while div <= no/2:
        if no % div == 0:
            flag = False
            break
        div = div + 1
    if flag and no > 1:
        print(no, "is Prime")
    no = no + 1

JavaScript Code

let no = 10;
let end = 50;

while (no <= end) {
    let div = 2;
    let flag = true;

    while (div <= no / 2) {
        if (no % div === 0) {
            flag = false;
            break;
        }
        div = div + 1;
    }

    if (flag && no > 1) {
        console.log(no + " is Prime");
    }

    no = no + 1;
}

Java Code

public class PrimeNumbers {
    public static void main(String[] args) {

        int no = 10;
        int end = 50;

        while (no <= end) {
            int div = 2;
            boolean flag = true;

            while (div <= no / 2) {
                if (no % div == 0) {
                    flag = false;
                    break;
                }
                div = div + 1;
            }

            if (flag && no > 1) {
                System.out.println(no + " is Prime");
            }

            no = no + 1;
        }
    }
}

Output

How to find GCD using a simple looping approach

The Greatest Common Divisor (GCD) is the largest number that divides two or more numbers without leaving a remainder.

For example:
GCD of 10 and 20 is 10

Python Code

no1 = 10
no2 = 20

small = no1 if no1 < no2 else no2
gcd = 0
div = 2

while div <= small:
    if no1 % div == 0 and no2 % div == 0:
        gcd = div
    div = div + 1

print("GCD is:", gcd)

JavaScript Code

let no1 = 10;
let no2 = 20;

let small = (no1 < no2) ? no1 : no2;
let gcd = 0;
let div = 2;

while (div <= small) {
    if (no1 % div === 0 && no2 % div === 0) {
        gcd = div;
    }
    div = div + 1;
}

console.log("GCD is:", gcd);

Java Code

public class GCDExample {
    public static void main(String[] args) {

        int no1 = 10;
        int no2 = 20;

        int small = (no1 < no2) ? no1 : no2;
        int gcd = 0;
        int div = 2;

        while (div <= small) {
            if (no1 % div == 0 && no2 % div == 0) {
                gcd = div;
            }
            div = div + 1;
        }

        System.out.println("GCD is: " + gcd);
    }
}

Output

I Hooked My Autonomous AI Outreach Swarm to Notion via MCP – It Reports Every Cycle in Real-Time

I Hooked My Autonomous AI Outreach Swarm to Notion via MCP — It Reports Every Cycle in Real-Time

Submission for the Notion MCP Challenge.

What I Built

NEXUS → Notion MCP Bridge: a Python client that connects an autonomous AI outreach swarm to a Notion database using the official @notionhq/notion-mcp-server over stdio transport — no REST API, pure Model Context Protocol.

Every 90 seconds, the swarm runs a cycle: scrape a live Reddit thread, write a reply, score it 0.0–1.0. Every passing cycle becomes a Notion database page automatically, giving me a real-time command center to review AI-generated content before it goes live.

GitHub: https://github.com/fliptrigga13/nexus-notion-mcp

The Problem

I run an autonomous outreach swarm that generates Reddit replies for my AI monitoring product. The swarm runs overnight. By morning I had a JSON file with 200 entries and no way to review them at a glance.

Notion was the obvious answer — a queryable, filterable view with a checkbox to track what was posted. The question was how to connect them.

How the Notion MCP Bridge Works

The key design decision: use the official Notion MCP server as the transport layer, not the REST API directly.

AI Swarm (Python) → cycles.json → MCP Bridge → notion-mcp-server (stdio) → Notion DB

The bridge spawns notion-mcp-server as a subprocess and communicates over stdin/stdout using JSON-RPC 2.0:

class NotionMCPClient:
    def start(self):
        env = {**os.environ, "NOTION_API_KEY": self._api_key}
        self._proc = subprocess.Popen(
            ["notion-mcp-server", "--transport", "stdio"],
            stdin=subprocess.PIPE,
            stdout=subprocess.PIPE,
            env=env,
            text=True,
        )
        self._initialize()

    def _initialize(self):
        self._send({
            "jsonrpc": "2.0",
            "id": self._next_id(),
            "method": "initialize",
            "params": {
                "protocolVersion": "2024-11-05",
                "capabilities": {},
                "clientInfo": {"name": "nexus-notion-mcp-bridge", "version": "1.0"},
            },
        })

Once initialized, the bridge calls notion_create_page for each new cycle:

def create_page(self, database_id: str, cycle: dict) -> bool:
    resp = self._send({
        "jsonrpc": "2.0",
        "id": self._next_id(),
        "method": "tools/call",
        "params": {
            "name": "notion_create_page",
            "arguments": {
                "parent": {"database_id": database_id},
                "properties": {
                    "Name": {"title": [{"text": {"content": title}}]},
                    "Score": {"number": score},
                    "Posted": {"checkbox": posted},
                    "Timestamp": {"date": {"start": ts}},
                },
                "children": [body_block]
            },
        },
    })
    return resp and not resp.get("error")

The Notion Database

Each row captures the full cycle: score, whether it was posted, timestamp, thread context, and the full outreach copy in the page body. The database becomes a live review queue — filter by Posted = false, sort by Score descending.

Setup

npm install -g @notionhq/notion-mcp-server

export NOTION_API_KEY=your_token
export NOTION_DATABASE_ID=your_db_id

python nexus_notion_mcp_bridge.py          # continuous polling
python nexus_notion_mcp_bridge.py --once   # sync once and exit

Why MCP Instead of REST

The REST API would have worked. But using the MCP server as the transport layer means:

  1. Any MCP-compatible AI client can now interact with the same Notion workspace
  2. The bridge is swappable — swap notion-mcp-server for any other MCP server and the Python client stays the same
  3. The stdio transport keeps everything local — no HTTP overhead, no additional auth surface

The MCP protocol is the right abstraction for AI-to-tool communication. The swarm is an AI system, Notion is the tool, MCP is the protocol that connects them.

What’s Next

  • Add notion_update_page when a cycle gets posted to sync the checkbox back
  • Build a filter view surfacing only high-scoring unposted entries
  • Connect the REFLECT agent’s insights to a Notion notes page

GitHub: https://github.com/fliptrigga13/nexus-notion-mcp

Stop Paying $200/Month for Rank Tracking — Automate It with Apify in 30 Minutes

If you’re tracking keyword rankings for a site or client, you’ve hit the same wall: rank trackers are expensive. SEMrush starts at $130/month. Ahrefs at $99. Moz at $99.

But here’s what those tools are actually doing: fetching Google HTML and parsing it. That’s a solved engineering problem — and you can automate it yourself for about $1/month.

I built this pipeline to track 47 keywords across 3 small client sites. It runs every Monday without me touching it and logs results to a Google Sheet. This article shows you exactly how to replicate it.

The Real Pain

You have a site. You want to know if it’s ranking for 20–50 keywords. You don’t need competitive intelligence dashboards or backlink graphs. You just need: “Where does my site show up for this query today?”

Manual checking doesn’t work. You forget. You get inconsistent results based on your location, login state, and device. After keyword #10, you stop doing it entirely.

Scheduled automation fixes all three problems.

What We’re Building

A simple 3-part pipeline:

[Apify Actor] → runs on schedule → [webhook] → [n8n workflow] → [Google Sheet]
  1. Apify’s Google SERP scraper fetches real search results on a cron schedule
  2. Apify webhook fires when the run succeeds and triggers n8n
  3. n8n workflow fetches the dataset, finds your domain’s rank, and appends a row to Google Sheets

No servers. No databases. No DevOps. All free or near-free.

Step 1: Set Up the Actor

The actor: lanky_quantifier/google-serp-scraper — community-built, 98.3% success rate across 172+ external runs in the last 30 days. Reliable enough to trust for weekly tracking.

Input JSON:

{
  "queries": [
    "best python web scraping tutorial",
    "apify vs scrapy",
    "automate google search python"
  ],
  "countryCode": "US",
  "languageCode": "en",
  "maxPagesPerQuery": 1
}

Run it once manually to verify your keywords return data. Check that your domain appears in organicResults. If you’re not in the top 10, organicResults won’t contain your URL — that’s expected and useful signal.

Step 2: Understand the Output

Each result looks like:

{
  "searchQuery": { "term": "best python web scraping tutorial" },
  "organicResults": [
    { "position": 1, "url": "https://realpython.com/...", "title": "..." },
    { "position": 2, "url": "https://yourdomain.com/...", "title": "..." }
  ]
}

To find your rank across all keywords:

def find_rankings(results, your_domain):
    """
    Returns a list of (keyword, position_or_None) tuples.
    position is None if domain not found in top 10.
    """
    rankings = []
    for item in results:
        keyword = item["searchQuery"]["term"]
        position = None
        for result in item.get("organicResults", []):
            if your_domain in result.get("url", ""):
                position = result["position"]
                break  # Found it — stop scanning this keyword's results
        rankings.append((keyword, position))
    return rankings

Note: The outer loop iterates keywords; the inner loop scans positions within that keyword’s results. The break is inside the inner loop — we stop scanning positions once we find the domain, then move to the next keyword. Don’t nest your return inside the outer loop or you’ll only ever process the first keyword.

Step 3: Automate with n8n + Google Sheets

3a. Set up the Google Sheet

Create a sheet called Rankings with these headers:

Date Keyword Position URL

3b. Create the n8n workflow

n8n is free to self-host. The workflow has 4 nodes:

  1. Webhook trigger — receives the Apify notification
  2. HTTP Request — fetches the run’s dataset from Apify API:
   GET https://api.apify.com/v2/datasets/{{$json.resource.defaultDatasetId}}/items
  1. Code node — finds your domain’s position (same logic as the Python snippet above, in JavaScript)
  2. Google Sheets node — appends rows

3c. Wire the Apify webhook

In Apify → Actor → Integrations → Webhooks:

  • Event type: ACTOR.RUN.SUCCEEDED
  • URL: Your n8n webhook trigger URL
  • Payload template (default is fine — n8n gets resource.defaultDatasetId from it)

That’s it. Now every time the scheduled run completes successfully, the sheet gets updated.

Step 4: Schedule the Actor

In Apify → Actor → Schedules, create a schedule using cron syntax:

0 9 * * 1

Every Monday at 9am UTC. Your data arrives before the workweek begins.

What This Costs

Component Cost
Apify free tier $5/month credit included
~50 keyword checks/week ~$0.50–$1.00/month
Google Sheets Free
n8n (self-hosted) Free
Total ~$1/month

vs. $99–$200/month for a SaaS rank tracker.

The math works because PAY_PER_EVENT billing means you only pay for actual compute — not for the product team, sales team, and VC returns baked into SaaS pricing.

What You See After 4 Weeks

Date Keyword Position URL
Mar 25 best python scraping tutorial 7 https://…
Mar 18 best python scraping tutorial 9 https://…
Mar 11 best python scraping tutorial 12 https://…
Mar 4 best python scraping tutorial Not in top 10 —

Position 12 → 7 in 3 weeks. That’s actionable signal. You can chart it in Sheets in 2 minutes, or set up a Sheets formula to alert you when any keyword drops below position 15.

Caveats Worth Knowing

Geo variance: Google results differ by location. The countryCode input pins the location, but results can still vary slightly. Good enough for trend tracking; not reliable for precise position reporting to clients who check manually.

Top 10 only: maxPagesPerQuery: 1 returns ~10 organic results per keyword. If you’re not on page 1, you’ll see null — which is still useful (“not ranking yet”).

Google’s anti-bot measures: The actor handles proxy rotation. You don’t need to manage this, but it’s why running this at home without proxies tends to fail. The Apify platform’s residential proxy pool is what makes the 98.3% success rate possible.

The Pattern Scales Further

The same architecture works for other data types:

  • Price monitoring → swap for lanky_quantifier/amazon-product-scraper, track competitor prices weekly
  • Job market signals → use lanky_quantifier/linkedin-job-scraper to track skill demand in your market
  • Content gap analysis → run SERP checks on competitor brand keywords to find topics they’re ranking for that you’re not

The Apify platform handles proxies, browser rendering, and rate limiting. You write the business logic.

Get Started

  1. Create a free Apify account — $5/month credit, no card required
  2. Open lanky_quantifier/google-serp-scraper and run it with your keywords
  3. Set up the Google Sheet + n8n workflow (~20 minutes)
  4. Add the schedule and webhook

The first week you’ll catch a ranking change you’d have missed. By month two, you’ll have a trend line that tells you whether your content efforts are actually working.

Stack preferences vary — the same pattern works with Airtable, Notion, or a plain SQLite file on a VPS. The actor output is JSON. Pipe it wherever you want.

I built my own icon platform after getting frustrated with FontAwesome — here’s how each feature came to life

I’m a 20-year-old student from Vietnam. I built Viconic (viconic.io.vn) alone, with no team and no sponsor. Every single feature came from a real frustration I had while building web projects.
Here’s the story.

It started with FontAwesome

When I first learned web development, I used FontAwesome like everyone else. But the kit builder was confusing — I couldn’t figure out how to generate a CDN link on my own. I ended up having to go to cdnjs.com just to grab a script tag. The free tier also had very few icons.
At the time, I had no idea SVGrepo or Iconify even existed.
So I started building my own thing.

Every feature came from a real problem

SVG injection instead of font icons

My first approach was to use icomoon to generate font icons. But I quickly ran into a problem — font-based icons can’t render multicolor icons accurately. Colors get lost or merged.
So I switched to SVG injection. Icons load directly via a custom tag from the CDN, similar to how Iconify works. Bundle size stays at 0KB since nothing is bundled — icons are fetched at runtime.

Kit builder for offline use

I study at a university where the internet connection is often unstable or completely unavailable. I needed a way to prepare a set of icons in advance and use them offline — similar to icomoon’s kit system, but without the font limitations.
So I built a kit builder. You pick the icons you need, give the kit a name, and Viconic generates a custom CDN URL for that kit. Load that URL once and all your icons are ready — even offline after the first load.

Inline SVG editor

Every time I needed to inspect or quickly tweak an icon, I’d open a new tab and go to svgviewer.dev. After doing this dozens of times, I got tired of it.
So I built an SVG editor directly into Viconic. You can adjust colors, size, and preview the result in real time — without leaving the page.

Fast mode

Sometimes I just need to copy an icon quickly. Opening a detail modal every single time felt unnecessary.
So I added fast mode. When enabled, clicking an icon copies it instantly — no modal, no extra steps.

What Viconic has now — completely free

  • 200,000+ icons from Lucide, Tabler, FontAwesome, and many more
  • CDN + npm package with zero bundle size
  • SVG injection via custom tag
  • Kit builder — create your own icon set with a custom CDN URL
  • Inline SVG editor — adjust colors and size directly in the browser
  • Copy as JSX, SVG, HTML, or Tailwind class in one click
  • Fast mode — click to copy instantly
  • Individual page for each icon — download SVG or PNG

Why I’m sharing this

I’m not sharing this because Viconic is perfect. It’s still in open beta and there’s a lot left to build.
I’m sharing because every feature on this list came from a real problem I personally ran into. No market research, no competitor analysis — just solving my own frustrations one by one.
If you’ve ever felt like existing icon tools were either too complex, too limited on the free tier, or just not designed for how you actually work — Viconic might be worth a try.
👉 https://viconic.io.vn
I’d love honest feedback — what’s missing, what’s broken, what would make you actually use this over SVGrepo or Iconify.

React Native in 2026: The New Architecture Guide

React Native keeps evolving. With React Native 0.87, the New Architecture is standard, Hermes and Fabric are fully mature, and building high-performance apps is easier than ever.

⚡ What’s New in 2026?
1️⃣ JSI (JavaScript Interface)
Direct JS ↔ C++ communication
No JSON serialization → faster interop
Real-time processing (e.g., VisionCamera handles 30MB frames at 2GB/s)
2️⃣ Fabric Renderer
Synchronous UI pipeline → smooth 60 FPS
Faster touch responses & less UI congestion
Full support for React 18 concurrent features
3️⃣ TurboModules
Lazy-loaded native modules
Faster startup & lower memory usage
📊 Real-World Gains
Shopify: 10% faster Android launch, 3% iOS
Memory dropped 85MB → 52MB
Smooth animations even on heavy screens
🛠 Key Features (RN 0.83 → 0.87)
Hermes V1/V2 improvements → faster TTI & bundle loading
Expo Go updates → instant testing on devices
Reanimated 4 & FlashList 2 → smoother animations & lists
TypeScript first-class support → better IDE autocomplete
🔧 Migration Tips
Audit dependencies → ensure New Architecture compatibility
Upgrade gradually → enable flags per platform
Migrate native modules → TurboModules + Codegen
Monitor performance → Flipper, frame rates, startup times
🎓 Best Practices
Use React concurrent features (useTransition)
Use useLayoutEffect for synchronous measurements
Lazy load TurboModules
Optimize with Hermes V1/V2
🔮 Looking Ahead

React Native 1.0 is near. Expect:

Stability & zero-breaking-change releases
Performance optimization
Better tooling & deeper platform integration

React Native 2026 = faster, smoother, and developer-friendly.

💬 Have you migrated to the New Architecture yet? Share your experience!

ReactNative #MobileDevelopment #JavaScript #Performance #NewArchitecture #TypeScript

I Connected Claude to My IBM i – And It Changed How I Think About Legacy Modernization

What happens when you give an AI direct access to a system most people have never heard of

There’s a weird disconnect in the AI conversation right now. Everyone’s talking about coding assistants, AI-powered DevOps, intelligent dashboards — but almost all of it assumes you’re running modern cloud infrastructure. Kubernetes, PostgreSQL, GitHub Actions. The usual stack.

Nobody’s talking about what happens when your critical business system runs on IBM i.

I work with IBM i every day. Warehouses, supply chains, enterprise systems that process millions of transactions and have been running for decades. These systems aren’t going anywhere. They’re stable, they’re fast, and they do exactly what they’re supposed to do.

But they’ve also been left out of the AI conversation entirely. And I kept wondering — does it have to be that way?

The problem with IBM i and modern tooling

If you’ve worked with IBM i, you know the feeling. You’re reading about some new tool or platform, nodding along, and then you hit the part where they assume everything lives in a REST API or a cloud database. And you think — okay, that doesn’t apply to me.

The irony is that IBM i systems often hold the most valuable data in an organization. Decades of transaction history. Real-time inventory positions. Production schedules that run 24/7. But getting that data out — or even just asking questions about it — still involves signing into a green screen, navigating menus, running queries manually, and interpreting raw output.

It’s not that the data isn’t accessible. Db2 for i is a perfectly capable database. QSYS2 SQL Services have made an incredible amount of system information queryable through standard SQL. The access is there. But the experience of getting to it hasn’t kept up with what’s happening everywhere else.

When I discovered MCP

Earlier this year I came across the Model Context Protocol — MCP for short. It’s an open standard that lets AI assistants connect to external tools and data sources. Anthropic published it, and it’s gained traction quickly. The idea is simple: you define tools that the AI can call, and the AI figures out when and how to use them based on what you’re asking.

The moment I understood how it worked, the wheels started turning.

What if I could write a handful of tools — run a SQL query, list active jobs, check system status, browse the IFS — and expose them to Claude through MCP? Not building a chatbot. Not training a model on IBM i documentation. Just giving an AI the ability to reach into the system and pull back real data.

So that’s what I did.

How it works (without the complexity you’d expect)

The architecture is embarrassingly simple, which is part of what makes it powerful.

You write a small Python server that runs on your local machine. This server connects to IBM i through ODBC — the same driver you probably already have installed if you use ACS (IBM i Access Client Solutions). The server defines tools as Python functions, each with a description that tells the AI what it does.

That’s the entire stack. Python on your PC, ODBC to IBM i, MCP to the AI. No changes on the IBM i side. No new programs to deploy. No RPG modifications. No service entries. Nothing.

The secret sauce, if there is one, is QSYS2 SQL Services. IBM has been quietly building out an incredible set of SQL-accessible system functions over the last several technology refreshes. Active job info, job logs, spool files, IFS statistics, system values, user profiles, message queues, data areas — almost everything you’d normally access through CL commands or green screen menus is now available as SQL table functions.

This means every tool in the MCP server is just a SQL query. Clean input, structured output. The AI gets JSON back instead of green-screen text, which it can actually interpret and reason about.

What it feels like to use

This is the part that genuinely surprised me.

I expected it to be useful. A faster way to run queries, maybe. A convenience layer. What I didn’t expect was how much it would change the way I interact with the system.

You can ask things like:

“Show me all libraries that start with PROD and tell me how many tables are in each one.”

And it just does it. Runs the query, counts the results, gives you a formatted answer. No navigating to WRKLIB. No typing SQL into STRSQL. Just a question and an answer.

Or:

“What’s the system health looking like right now? Anything I should worry about?”

It pulls CPU usage, memory, disk capacity, active job counts — and then interprets them. Not just raw numbers, but context. “CPU is at 23%, well within normal range. Disk usage on ASP 1 is at 71%, which is getting up there — you might want to keep an eye on that.”

The real magic happens when you chain things together in a conversation. You ask about active jobs for a specific user. Something looks odd. You say “check the job log for that one.” It knows which job you mean from the previous response. Then you say “has this user had issues before? Check their message queue.” And it does.

That kind of continuity — where context carries forward naturally — is something you can’t replicate with traditional tools. Every green screen interaction is stateless. You close the screen, the context is gone. Here, the AI holds onto the thread and builds on it.

The tools that matter most

After using this for a while, I’ve found that certain tools get used far more than others.

SQL queries are the backbone. Being able to say “find all orders from the last 48 hours where the quantity is over 500” and get results instantly — that alone would justify the setup. But when you combine it with the AI’s ability to interpret and summarize, it becomes something different. You’re not just querying data, you’re having a conversation about it.

System status is surprisingly useful. I used to check WRKACTJOB and WRKSYSSTS a few times a day. Now I just ask. And the AI remembers what “normal” looked like from previous checks, so it can flag when something changes.

Job logs are where the AI really shines. Reading job logs on IBM i is tedious — they’re dense, full of informational messages mixed in with the important stuff. The AI is genuinely good at scanning through a job log and picking out the messages that matter. “There are 47 messages in this job log. Most are routine. But there’s a CPF4131 at 14:23 indicating a file member not found, and a follow-up CPD0006 — that’s likely your issue.”

IFS browsing and file reading is the one I didn’t expect to use as much as I do. Being able to say “show me what’s in /home/myuser/exports and read the most recent CSV” is just faster than navigating the IFS through any other method.

Why QSYS2 is the unsung hero

I want to spend a moment on this because I think it’s underappreciated.

IBM has been building QSYS2 SQL Services for years. Every technology refresh adds more. And the beauty of it is that it turns everything on the system into structured, queryable data. You don’t need to parse command output. You don’t need screen-scraping programs. You just write SQL.

For this kind of AI integration, that’s everything. The AI needs structured data to reason about. Give it a blob of green-screen text and it’ll struggle. Give it a JSON array of job records with named fields and it’ll do exactly what you want.

If your shop hasn’t explored what’s available through QSYS2 lately, it’s worth looking. The coverage now is extensive — far beyond what most people realize. It’s one of the best things IBM has done for the platform in recent years, and projects like this show why.

What this means for IBM i shops

I’m not going to oversell this. It’s not going to replace your operators. It’s not going to automate your entire system. It’s a tool.

But it’s a tool that addresses something I’ve seen at every IBM i shop I’ve worked with: the knowledge bottleneck.

There are usually one or two people who really know the system. Who know which libraries matter, what the critical jobs are, where to look when something breaks. When those people are unavailable — or when they eventually leave — that knowledge walks out the door.

An AI that can query the system, interpret results, and carry context across a conversation doesn’t replace that expertise. But it makes it accessible to people who don’t have it yet. A junior developer can ask “what are the biggest tables in PRODLIB?” and get an immediate, meaningful answer. A manager can check on system health without learning CL commands. A new team member can explore the system conversationally instead of reading documentation that may or may not be current.

That accessibility matters. IBM i’s biggest challenge has never been capability — it’s been the perception that it’s impenetrable. Anything that makes it more approachable is a win for the platform’s long-term viability.

What I’d build next

This was a starting point. The obvious extensions:

  • Source member browsing — being able to read RPG or CL source through a conversation and have the AI explain what it does. Imagine onboarding new developers who can literally ask the AI “what does this program do?” while looking at the source.
  • Authority analysis — “who has access to this file?” is a question that takes too long to answer today.
  • PTF and system maintenance status — turning system administration checks into conversations.
  • Cross-referencing — “which programs use this file?” by querying object references through SQL Services.

The protocol makes this extensible. Adding a new capability is just adding a new function with a description. There’s no framework overhead, no deployment complexity. The hardest part is writing good SQL, and if you’re on IBM i, you’re already doing that.

The bigger picture

I started building this because I was curious. I kept building it because it genuinely made me more productive. But what excites me most is what it represents.

For years, IBM i modernization has been framed as “move off the platform” or “rewrite everything.” And both of those approaches are expensive, risky, and often unnecessary. The systems work. The data is valuable. The business logic is proven.

What MCP shows is that you can bring modern capabilities to IBM i without changing IBM i. You don’t need to rewrite your RPG. You don’t need to migrate your database. You don’t need to replace anything. You just need to build a bridge — a thin layer that translates between what the AI expects and what IBM i provides.

QSYS2 SQL Services is one half of that bridge. MCP is the other. And the fact that you can connect them with a few hundred lines of Python — no middleware, no platform changes, no vendor contracts — is exactly the kind of pragmatic modernization that actually works in enterprise environments.

Wrapping up

I’m not naive about the limitations. There are security considerations — you need to be thoughtful about what you expose and to whom. There’s the question of audit trails and compliance. And the AI will occasionally get a query wrong, just like any tool.

But the potential here is real. And it’s the kind of potential that doesn’t require permission from a steering committee or a six-month project plan. It’s a Saturday afternoon experiment that turns into something you use every day.

If you’re in an IBM i shop and you’ve been wondering where AI fits into your world, this might be the answer. Not a massive transformation initiative. Not a vendor platform. Just a conversation with your system that actually understands what you’re asking.

Jaya Krushna Mohapatra is a Warehouse Management Systems Architect focused on enterprise integrations, IBM i modernization, and scalable backend systems.