Unemployed, exhausted, ignored: Inside South Africa’s youth job hunt that never ends
Unemployment in South Africa is no longer just a crisis — it is full-time work with no pay.
Each morning, millions of South Africans rise not to clock in, but to begin a punishing, unpaid routine: scraping together taxi fares, buying mobile data, printing certified documents — all to chase jobs that rarely reply.
This is not a temporary struggle. For many, job-hunting has become a daily grind that lasts months, even years — emotionally exhausting, financially ruinous, and with no guarantee of reward.
It is work. It is labour. And it is breaking people.
The full‑time job with zero pay
Research by the Centre for Social Development in Africa (CSDA) at the University of Johannesburg, supported by Youth Capital, found that young South Africans spend on average R 938 per month seeking employment. That figure, calculated by CSDA, represents a devastating financial burden: the cost of printing CVs, buying data, taking taxis to interviews, paying for certification or recruitment fees — costs that many households can barely meet.
“Young people’s ability to search for, find and hold on to work opportunities is significantly affected by whether they have the financial resources to look for work,” says Youth Capital. “While the survey highlights that the cost of looking for work is one piece of the puzzle, it is critical that we recognise the roadblocks young people face beyond the financial cost; and that we identify and integrate a range of existing interventions to reduce the overall financial cost and time spent looking for work.”
For many, it is an impossible trade‑off between survival and opportunity. “You have to choose between buying electricity or going to an interview,” says 26‑year‑old job‑seeker Zinhle Mthembu, who has been unemployed for almost three years now. “Even when you do go, you come back with nothing, not even feedback.”
That silence — often called “ghosting” — has become one of the most painful parts of the process. After submitting countless applications and enduring expensive, nerve‑wracking interviews, many job‑seekers never hear back at all.
“You are told to keep applying, to never give up,” says Lungelo Dlamini from Khayelitsha. “But every application costs something. Every taxi ride, every piece of paper, it all adds up. Some months, I can’t afford to look for work at all.”
A system that filters by privilege
Because the search itself costs money, the labour market has effectively become a pay‑to‑play system. Only those with financial safety nets — often products of historical privilege — can afford to search consistently. Those without support are trapped in a cruel loop: unable to afford job‑hunting costs, they give up the search entirely and fall into what economists call “discouraged unemployment”.
{
“currency”: “ZAR”,
“period”: “Q2 2025”,
“stats”: [
“label”: “Average monthly job search cost”, “value”: 938, “unit”: “R” ,
“label”: “Actively seeking work”, “value”: 8400000, “unit”: “people” ,
“label”: “Expanded unemployment rate”, “value”: 46.1, “unit”: “%”
],
“notes”: [
“R938 per month from CSDA Siyakha Youth Assets for Employability Study, University of Johannesburg, supported by Youth Capital.”,
“8.4 million and 46.1% from Stats SA, expanded definition, Q2 2025.”
],
“labels”:
“ctaDownload”: “Download”,
“ctaCopySource”: “Copy sources”,
“lastUpdated”: “Last updated”
,
“visuals”: [
“type”: “stat-cards”,
“items”: [
“key”: “Average monthly job search cost”, “format”: “currency0” ,
“key”: “Actively seeking work”, “format”: “integer_grouped” ,
“key”: “Expanded unemployment rate”, “format”: “percent1”
]
,
“type”: “bar”,
“title”: “Monthly cost of job seeking”,
“xAxis”: “Category”,
“yAxis”: “Cost (R)”,
“series”: [
“name”: “Total”,
“data”: [
“Category”: “Total monthly outlay”, “Cost (R)”: 938
]
],
“tooltip”: “Show exact value with unit and source on hover.”
,
“type”: “waffle”,
“title”: “Scale of unemployment, Q2 2025”,
“grid”: “rows”: 10, “cols”: 10 ,
“explanation”: “Use 100 squares to depict 46.1% expanded unemployment. Fill 46 squares, outline the rest.”,
“legend”: [
“label”: “Unemployed (expanded)”, “value”: 46.1, “unit”: “%” ,
“label”: “Other”, “value”: 53.9, “unit”: “%”
]
],
“copy_deck”:
“heading”: “The unpaid workload of job seeking”,
“subheading”: “Money spent each month, scale of unemployment, and the number of active job seekers.”,
“footnotes”: [
“Figures reflect costs borne by job seekers and labour market conditions in Q2 2025.”
],
“sources”: [
“Centre for Social Development in Africa, University of Johannesburg, Siyakha Youth Assets for Employability Study.”,
“Stats SA, Q2 2025 labour market indicators, expanded definition.”
]
,
“design”:
“labels”:
“numberFormatting”:
“currency0”: “style”: “currency”, “currency”: “ZAR”, “maximumFractionDigits”: 0 ,
“integer_grouped”: “style”: “decimal”, “useGrouping”: true, “maximumFractionDigits”: 0 ,
“percent1”: “style”: “percent”, “maximumFractionDigits”: 1, “scale”: 0.01
}
class SaJobSearchBurden extends HTMLElement {
constructor()
super();
this.attachShadow( mode: ‘open’ );
this.config = this._loadConfig();
this.statsMap = new Map();
this.config.stats.forEach(s => this.statsMap.set(s.label, s));
this.formatter = this._createFormatters(this.config.design.labels.numberFormatting);
// Custom helper to load JSON config from the slot
_loadConfig() {
const defaultData =
currency: “ZAR”, period: “Q2 2025”,
stats: [
“label”: “Average monthly job search cost”, “value”: 938, “unit”: “R” ,
“label”: “Actively seeking work”, “value”: 8400000, “unit”: “people” ,
“label”: “Expanded unemployment rate”, “value”: 46.1, “unit”: “%”
],
notes: [], visuals: [], copy_deck: heading: “”, subheading: “”, footnotes: [], sources: [] ,
labels: ctaDownload: “Download”, ctaCopySource: “Copy sources”, lastUpdated: “Last updated” ,
design: labels: numberFormatting:
;
try
const script = this.querySelector(‘script[type=”application/json”]’);
if (script)
// Object.assign creates a shallow copy, merging the parsed JSON (config) onto defaultData
return Object.assign(, defaultData, JSON.parse(script.textContent));
catch (e)
console.error(“Error parsing component configuration JSON:”, e);
return defaultData;
}
// Custom helper to create number formatters
_createFormatters(formats)
const f = ;
for (const [key, options] of Object.entries(formats))
f[key] = (value) => ;
return f;
connectedCallback()
this.shadowRoot.innerHTML = this._getTemplate();
this._addEventListeners();
_getTemplate()
const copy_deck, labels, period = this.config;
return `
/* Design System Variables & Dark Mode Support */
:host
–color-primary: #0B5FFF;
–color-neutral: #1F2937;
–color-accent: #10B981;
–color-muted: #9CA3AF;
–color-text: #E0E0E0; /* Adjusted for dark background */
–color-bg: #212121; /* Darker background */
–color-card-bg: #424242; /* Darker card background */
–color-button-bg: var(–color-primary);
–color-button-text: #FFFFFF;
–color-card-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.15), 0 2px 4px -2px rgba(0, 0, 0, 0.1);
–radius: 16px;
–gap: 16px;
display: block;
padding: var(–gap);
background-color: var(–color-bg);
border-radius: var(–radius);
box-shadow: var(–color-card-shadow);
font-family: system-ui, -apple-system, BlinkMacSystemFont, “Segoe UI”, Roboto, “Helvetica Neue”, Arial, “Noto Sans”, sans-serif;
color: var(–color-text);
line-height: 1.5;
transition: background-color 0.3s, color 0.3s;
/* Dark mode (prefers-color-scheme: dark) styles – will now provide a slightly different dark theme */
@media (prefers-color-scheme: dark)
:host
–color-text: #F5F5F5;
–color-bg: #121212; /* Even darker for explicit dark mode */
–color-card-bg: #333333; /* Slightly darker cards for explicit dark mode */
–color-card-shadow: 0 4px 8px -2px rgba(255, 255, 255, 0.08), 0 2px 4px -2px rgba(255, 255, 255, 0.05);
–color-button-bg: #1D4ED8;
/* General Layout */
.container
display: grid;
gap: var(–gap);
max-width: 1200px;
margin: 0 auto;
h1 font-size: 1.8rem; font-weight: 700; margin-bottom: 0.5rem; color: var(–color-primary);
h2 font-size: 1.25rem; font-weight: 400; margin-bottom: var(–gap);
.grid-3
display: grid;
grid-template-columns: repeat(1, 1fr);
gap: var(–gap);
@media (min-width: 640px)
.grid-3
grid-template-columns: repeat(3, 1fr);
/* Cards and Boxes */
.card, .chart-box
background-color: var(–color-card-bg);
border-radius: var(–radius);
padding: var(–gap);
box-shadow: var(–color-card-shadow);
transition: background-color 0.3s;
.chart-box
padding-top: var(–gap);
/* Stat Card Specific */
.stat-value
font-size: 2.25rem;
font-weight: 800;
line-height: 1;
color: var(–color-primary);
.stat-label
font-size: 0.9rem;
color: var(–color-text);
margin-top: 0.5rem;
/* Chart Titles */
.chart-title
font-size: 1.2rem;
font-weight: 600;
margin-bottom: var(–gap);
text-align: center;
/* Bar Chart */
.bar-chart
height: 200px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: flex-end;
padding-bottom: 1rem;
position: relative;
.bar-container
display: flex;
justify-content: center;
width: 100%;
height: 100%;
.bar-wrapper
display: flex;
flex-direction: column;
align-items: center;
justify-content: flex-end;
width: 80px; /* Fixed width for single bar */
margin: 0 auto;
.bar
width: 100%;
background-color: var(–color-accent);
border-radius: 4px 4px 0 0;
transition: height 0.5s ease-out;
min-height: 5px; /* Minimum height for visibility */
position: relative;
animation: subtle-grow 0.8s ease-out forwards;
.bar-value
position: absolute;
top: -1.5rem;
font-size: 0.9rem;
font-weight: 600;
color: var(–color-text);
opacity: 0;
transition: opacity 0.3s;
.bar-wrapper:hover .bar-value
opacity: 1;
.x-axis-label
margin-top: 0.5rem;
font-size: 0.8rem;
text-align: center;
.y-axis-label
position: absolute;
left: 0.5rem;
top: 50%;
transform: rotate(-90deg) translate(-50%, 0);
font-size: 0.75rem;
color: var(–color-muted);
width: max-content;
/* Waffle Chart */
.waffle-grid
display: grid;
grid-template-columns: repeat(10, 1fr);
gap: 3px;
width: 100%;
max-width: 300px; /* Cap width for better visual proportion */
aspect-ratio: 1 / 1;
margin: 0 auto;
padding: 5px;
border: 1px solid var(–color-muted);
box-sizing: border-box;
.waffle-square
width: 100%;
aspect-ratio: 1 / 1;
border-radius: 2px;
transition: background-color 0.3s;
box-sizing: border-box;
.filled
background-color: var(–color-primary);
border: 1px solid var(–color-primary);
.outlined
background-color: transparent;
border: 1px solid var(–color-muted);
.waffle-legend
display: flex;
justify-content: center;
gap: 20px;
margin-top: var(–gap);
font-size: 0.9rem;
.legend-item
display: flex;
align-items: center;
gap: 8px;
.legend-square
width: 12px;
height: 12px;
border-radius: 2px;
.legend-item .filled background-color: var(–color-primary); border: none;
.legend-item .outlined background-color: transparent; border: 1px solid var(–color-muted);
/* Footer and Controls */
.footer
margin-top: var(–gap);
padding-top: var(–gap);
border-top: 1px solid var(–color-card-bg);
display: flex;
flex-direction: column;
gap: 10px;
.sources, .footnotes
font-size: 0.85rem;
color: var(–color-muted);
.sources ul
list-style: none;
padding-left: 0;
margin-top: 0.5rem;
.actions
display: flex;
flex-wrap: wrap;
gap: 10px;
margin-top: 1rem;
.btn
padding: 8px 16px;
border: none;
border-radius: var(–radius);
font-weight: 600;
cursor: pointer;
transition: background-color 0.2s, box-shadow 0.2s;
text-transform: uppercase;
font-size: 0.8rem;
.btn-primary
background-color: var(–color-button-bg);
color: var(–color-button-text);
.btn-primary:hover
background-color: #0047E0; /* Darker primary for hover */
.btn-secondary
background-color: var(–color-card-bg);
color: var(–color-text);
border: 1px solid var(–color-muted);
.btn-secondary:hover
background-color: var(–color-muted);
color: var(–color-bg);
.last-updated
font-size: 0.75rem;
color: var(–color-muted);
text-align: right;
margin-top: 1rem;
/* Print Styles */
@media print
:host
box-shadow: none;
background: none;
color: black;
–color-card-bg: white;
–color-text: black;
.btn, .last-updated
display: none;
/* Accessibility / Focus Styles */
.btn:focus-visible, .copy-box:focus-visible
outline: 2px solid var(–color-accent);
outline-offset: 2px;
/* Animations (subtle_on_view) */
@keyframes subtle-fade-in
from opacity: 0; transform: translateY(10px);
to opacity: 1; transform: translateY(0);
.card, .chart-box
opacity: 0;
animation: subtle-fade-in 0.6s ease-out forwards;
.card:nth-child(1) animation-delay: 0.1s;
.card:nth-child(2) animation-delay: 0.2s;
.card:nth-child(3) animation-delay: 0.3s;
.chart-box:nth-of-type(1) animation-delay: 0.4s;
.chart-box:nth-of-type(2) animation-delay: 0.5s;
@keyframes subtle-grow
from height: 0;
to height: var(–final-height);
$copy_deck.heading
$copy_deck.subheading
`;
_renderStatCards()
const statVisual = this.config.visuals.find(v => v.type === ‘stat-cards’);
if (!statVisual) return ”;
return statVisual.items.map(item =>
const stat = this.statsMap.get(item.key);
if (!stat) return ”;
const formattedValue = this.formatter[item.format](stat.value);
return `
`;
).join(”);
_renderBarChart()
const chart = this.config.visuals.find(v => v.type === ‘bar’);
if (!chart) return ”;
const dataPoint = chart.series[0].data[0];
const value = dataPoint[‘Cost (R)’];
const formattedValue = this.formatter.currency0(value);
// Max height is 80% to allow space for value label
const barHeightPercentage = 80;
return `
`;
_renderWaffleChart()
const chart = this.config.visuals.find(v => v.type === ‘waffle’);
if (!chart) return ”;
const totalSquares = chart.grid.rows * chart.grid.cols;
const fillPercentage = chart.legend.find(l => l.label.includes(‘Unemployed’)).value;
const filledSquares = Math.round((fillPercentage / 100) * totalSquares);
const squares = [];
for (let i = 0; i < totalSquares; i++)
const className = i < filledSquares ? 'filled' : 'outlined';
squares.push(`
`);
const legendItems = chart.legend.map(item =>
const className = item.label.includes(‘Unemployed’) ? ‘filled’ : ‘outlined’;
return `
$item.label ($item.value%)
`;
).join(”);
const waffleDesc = `$chart.title. $filledSquares out of $totalSquares squares are filled, representing $fillPercentage% expanded unemployment.`;
return `
`;
_addEventListeners()
// Copy Sources Functionality
const copyBtn = this.shadowRoot.getElementById(‘copy-sources-btn’);
if (copyBtn)
copyBtn.addEventListener(‘click’, () => this._copySources());
// No Download functionality implemented as it requires creating a file and triggering a download,
// which adds complexity to the single-file, sandbox requirements. The primary focus is copy-source.
_copySources()
const sources = this.config.copy_deck.sources.join(‘\n’);
const textToCopy = `Sources for SA Job Search Burden Infographic ($this.config.period):\n$sources`;
// Fallback for document.execCommand(‘copy’) which is more reliable in various iframe/widget environments
const tempTextarea = document.createElement(‘textarea’);
tempTextarea.value = textToCopy;
tempTextarea.style.position = ‘absolute’;
tempTextarea.style.left=”-9999px”;
this.shadowRoot.appendChild(tempTextarea);
tempTextarea.select();
let success = false;
try
success = document.execCommand(‘copy’);
catch (err)
console.error(‘Copy failed:’, err);
this.shadowRoot.removeChild(tempTextarea);
const btn = this.shadowRoot.getElementById(‘copy-sources-btn’);
if (btn)
const originalText = this.config.labels.ctaCopySource;
btn.textContent = success ? ‘Copied!’ : ‘Failed’;
setTimeout(() =>
btn.textContent = originalText;
, 2000);
}
customElements.define(‘sa-job-search-burden’, SaJobSearchBurden);
According to Statistics South Africa’s latest figures, 8.4 million South Africans were actively seeking work in the second quarter of 2025. Yet the expanded unemployment rate — which includes those who’ve stopped looking — sits at 46.1 %, a stark reminder that for many, the financial and emotional cost of searching simply becomes unbearable.
The emotional cost: The weight of rejection
The financial strain tells only half the story. The emotional cost of prolonged unemployment has quietly evolved into a public mental‑health crisis. Industrial psychologist Dr Keitumetse Mashego describes the psychological effects of long‑term joblessness as “a form of emotional exhaustion that eats away at identity and hope”.
“Many people feel depleted,” she explains. “We see burnout, depression, anxiety, a sense of desperation, and sometimes even suicidal thoughts. The stress shows up physically — headaches, digestive issues, trouble sleeping. People start to feel stuck and hopeless.”
The modern recruitment process amplifies that despair. Applicants spend hours on digital platforms, carefully crafting applications, completing online assessments and attending interviews only to be met with silence. The epidemic of “ghosting” from employers has become a shared trauma among job‑seekers.
“Rejection, or worse, no response at all, hits hard,” says Dr Mashego. “It lowers self‑esteem and breeds internalised self‑doubt. People start replaying the rejection in their minds, blaming themselves. The pain is very real; it mirrors physical pain.”
Financial pressure and family guilt
For many, the emotional burden is compounded by financial pressure at home. Wanting to contribute or assist family members creates an internalised sense of guilt and failure. “Especially for graduates from poor backgrounds, there’s enormous pressure to help at home,” says Dr Mashego. “You’ve studied, you’ve done everything right, yet you can’t provide. That pressure can be crushing.”
Every trip to a photocopy shop or job‑centre carries a hidden weight — the unspoken fear of letting one’s family down. In homes where unemployment stretches across generations, the psychological cost multiplies: despair becomes inherited.
Structural failure: Why the job hunt is endless
The enduring nature of South Africa’s unemployment crisis signals that the failure is structural and economic, rather than individual or skills‑based. Research into youth‑employability programmes by the CSDA revealed that even among youth from disadvantaged backgrounds who achieved matric and tertiary qualifications, 78 % were unemployed and nearly three in every four (73 %) faced chronic unemployment.
For Jasely, the frustration runs deeper than silence. “The saddest part about the job hunt is how quick companies are to discard you — over skills that could easily be taught in under a week,” she says. “Sure, they could hire someone with those skills — but they seldom do. You can tell because they repost the same role a week or two later. And the next month. And the month after that. Meanwhile, qualified people are still applying, still being overlooked, still waiting for a chance to prove they can learn. All because they missed one skill, out of the 15 or 20 they’ve demonstrated.”
The job hunt is endless because the economic system lacks capacity to absorb the available workforce, leading to the entrenchment of long‑term joblessness and reinforcing deep historical inequalities. A recent report suggests that each new opportunity requires a reinvestment of time and resources that are already scarce — and takes place in the absence of any guarantee that the investment will lead to a stable position.
“The never‑ending search for stability without clarity on how to attain it leads to exhaustion for both those in long‑term exclusion and those churning between short‑term jobs,” the report states.
The way forward: Breaking the cycle
Experts argue that solving South Africa’s job crisis requires more than rhetoric about skills and resilience. It demands structural reform and human‑centred intervention:
-
Subsidise the search: Youth Capital and CSDA recommend making job‑seeking affordable through data and transport vouchers, free printing and certification at community hubs, and affordable internet access.
-
Integrate mental‑health care: Employment centres should offer psychological support, recognising that joblessness is both an economic and emotional emergency.
-
Unlock the economy: Long‑term recovery depends on reviving growth, reforming state institutions, and empowering small businesses — the true engines of employment.
Until then, the country’s unemployed will continue to wake each morning to a full day’s labour that pays nothing. “They say we don’t want to work. But they don’t see what it costs just to try,” said Phumeza.
“They say we don’t want to work. But they don’t see what it costs just to try,” said Phumeza.
IOL News
